J'ai une classe de base MyBase qui contient une fonction virtuelle pure:
void PrintStartMessage() = 0
Je veux que chaque classe dérivée l'appelle dans son constructeur
puis je le mets dans le constructeur de la classe de base (MyBase
)
class MyBase
{
public:
virtual void PrintStartMessage() =0;
MyBase()
{
PrintStartMessage();
}
};
class Derived:public MyBase
{
public:
void PrintStartMessage(){
}
};
void main()
{
Derived derived;
}
mais je reçois une erreur de l'éditeur de liens.
this is error message :
1>------ Build started: Project: s1, Configuration: Debug Win32 ------
1>Compiling...
1>s1.cpp
1>Linking...
1>s1.obj : error LNK2019: unresolved external symbol "public: virtual void __thiscall MyBase::PrintStartMessage(void)" (?PrintStartMessage@MyBase@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" (??0MyBase@@QAE@XZ)
1>C:\Users\Shmuelian\Documents\Visual Studio 2008\Projects\s1\Debug\s1.exe : fatal error LNK1120: 1 unresolved externals
1>s1 - 2 error(s), 0 warning(s)
Je veux forcer toutes les classes dérivées à ...
A- implement it
B- call it in their constructor
Comment je dois le faire?
De nombreux articles expliquent pourquoi vous ne devez jamais appeler des fonctions virtuelles dans un constructeur et un destructeur en C++. Jetez un coup d'œil ici et ici pour plus de détails sur ce qui se passe en coulisse lors de tels appels.
En bref, les objets sont construits de la base au dérivé. Ainsi, lorsque vous essayez d'appeler une fonction virtuelle à partir du constructeur de la classe de base, le remplacement des classes dérivées n'a pas encore eu lieu car les constructeurs dérivés n'ont pas encore été appelés.
Il est dangereux d’appeler une méthode abstraite pure à partir d’une méthode dérivée pendant que cet objet est encore en cours de construction. C'est comme essayer de faire le plein d'essence dans une voiture, mais cette voiture est toujours sur la chaîne de montage et le réservoir d'essence n'a pas encore été installé.
Le mieux que vous puissiez faire est de construire d'abord votre objet, puis d'appeler la méthode après:
template <typename T>
T construct_and_print()
{
T obj;
obj.PrintStartMessage();
return obj;
}
int main()
{
Derived derived = construct_and_print<Derived>();
}
Vous ne pouvez pas le faire comme vous l'imaginez, car vous ne pouvez pas appeler des fonctions virtuelles dérivées à partir du constructeur de la classe de base. L'objet n'est pas encore du type dérivé. Mais vous n'avez pas besoin de faire ça.
Supposons que vous vouliez faire quelque chose comme ceci:
class MyBase {
public:
virtual void PrintStartMessage() = 0;
MyBase() {
printf("Doing MyBase initialization...\n");
PrintStartMessage(); // ⚠ UB: pure virtual function call ⚠
}
};
class Derived : public MyBase {
public:
virtual void PrintStartMessage() { printf("Starting Derived!!!\n"); }
};
La trace d'exécution souhaitée serait:
Doing MyBase initialization...
Starting Derived!!!
Mais c’est à ça que servent les constructeurs! Il suffit de supprimer la fonction virtuelle et de faire en sorte que le constructeur de la dérivée fasse le travail!
class MyBase {
public:
MyBase() { printf("Doing MyBase initialization...\n"); }
};
class Derived : public MyBase {
public:
Derived() { printf("Starting Derived!!!\n"); }
};
Le résultat est, bien, ce que nous attendions:
Doing MyBase initialization...
Starting Derived!!!
Cela n’impose cependant pas les classes dérivées pour implémenter explicitement la fonctionnalité PrintStartMessage
. Mais d’un autre côté, réfléchissez-y à deux fois si cela est nécessaire, car sinon, ils peuvent toujours fournir une implémentation vide.
Comme indiqué ci-dessus, si vous devez appeler PrintStartMessage
avant que le Derived
ait été constructeur, vous ne pouvez pas le faire car il n'y a pas encore d'objet Derived
pour lequel PrintStartMessage
doit être appelé. Cela n'aurait aucun sens d'exiger que PrintStartMessage
soit un membre non statique, car il n'aurait aucun accès à aucun des membres de données Derived
.
Alternativement, nous pouvons en faire un membre statique comme suit:
class MyBase {
public:
MyBase() {
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase {
public:
static void PrintStartMessage() { printf("Derived specific message.\n"); }
};
Une question naturelle se pose de savoir comment on va l'appeler.
Je peux voir qu'il existe deux solutions: l'une est similaire à celle de @greatwolf, où vous devez l'appeler manuellement. Mais maintenant, puisqu'il s'agit d'un membre statique, vous pouvez l'appeler avant qu'une instance de MyBase
ait été construite:
template<class T>
T print_and_construct() {
T::PrintStartMessage();
return T();
}
int main() {
Derived derived = print_and_construct<Derived>();
}
La sortie sera
Derived specific message.
Doing MyBase initialization...
Cette approche force toutes les classes dérivées à implémenter PrintStartMessage
. Malheureusement, ce n’est vrai que lorsque nous les construisons avec notre fonction d’usine ... ce qui est un inconvénient majeur de cette solution.
La deuxième solution consiste à recourir au modèle de modèle curieusement récurrent (CRTP). En indiquant MyBase
le type d'objet complet à la compilation, il peut effectuer l'appel à partir du constructeur:
template<class T>
class MyBase {
public:
MyBase() {
T::PrintStartMessage();
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase<Derived> {
public:
static void PrintStartMessage() { printf("Derived specific message.\n"); }
};
La sortie est conforme aux attentes, sans qu'il soit nécessaire d'utiliser une fonction d'usine dédiée.
Lorsque MyBase
est en cours d'exécution, son accès aux membres est déjà correct. Nous pouvons faire en sorte que PrintStartMessage
puisse accéder à la MyBase
qui l’a appelée:
template<class T>
class MyBase {
public:
MyBase() {
T::PrintStartMessage(this);
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase<Derived> {
public:
static void PrintStartMessage(MyBase<Derived> *p) {
// We can access p here
printf("Derived specific message.\n");
}
};
Ce qui suit est également valide et très fréquemment utilisé, même s’il est un peu dangereux:
template<class T>
class MyBase {
public:
MyBase() {
static_cast<T*>(this)->PrintStartMessage();
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase<Derived> {
public:
void PrintStartMessage() {
// We can access *this member functions here, but only those from MyBase
// or those of Derived who follow this same restriction. I.e. no
// Derived data members access as they have not yet been constructed.
printf("Derived specific message.\n");
}
};
Une autre option consiste à redéfinir un peu votre code. IMO celui-ci est en fait la solution préférée si vous devez absolument appeler une PrintStartMessage
surchargée à partir de la construction MyBase
.
Cette proposition consiste à séparer Derived
de MyBase
comme suit:
class ICanPrintStartMessage {
public:
virtual ~ICanPrintStartMessage() {}
virtual void PrintStartMessage() = 0;
};
class MyBase {
public:
MyBase(ICanPrintStartMessage *p) : _p(p) {
_p->PrintStartMessage();
printf("Doing MyBase initialization...\n");
}
ICanPrintStartMessage *_p;
};
class Derived : public ICanPrintStartMessage {
public:
virtual void PrintStartMessage() { printf("Starting Derived!!!\n"); }
};
Vous initialisez MyBase
comme suit:
int main() {
Derived d;
MyBase b(&d);
}
Vous ne devriez pas appeler une fonction virtual
dans un constructeur. Période . Vous devrez trouver une solution de contournement, comme créer PrintStartMessage
non -virtual
et placer l'appel explicitement dans chaque constructeur.
Je sais que c'est une vieille question, mais je suis tombé sur la même question en travaillant sur mon programme.
Si votre objectif est de réduire la duplication de code en demandant à la classe de base de gérer le code d'initialisation partagé tout en demandant aux classes dérivées de spécifier le code qui leur est propre dans une méthode virtuelle pure, c'est ce que j'ai décidé.
#include <iostream>
class MyBase
{
public:
virtual void UniqueCode() = 0;
MyBase() {};
void init(MyBase & other)
{
std::cout << "Shared Code before the unique code" << std::endl;
other.UniqueCode();
std::cout << "Shared Code after the unique code" << std::endl << std::endl;
}
};
class FirstDerived : public MyBase
{
public:
FirstDerived() : MyBase() { init(*this); };
void UniqueCode()
{
std::cout << "Code Unique to First Derived Class" << std::endl;
}
private:
using MyBase::init;
};
class SecondDerived : public MyBase
{
public:
SecondDerived() : MyBase() { init(*this); };
void UniqueCode()
{
std::cout << "Code Unique to Second Derived Class" << std::endl;
}
private:
using MyBase::init;
};
int main()
{
FirstDerived first;
SecondDerived second;
}
La sortie est:
Shared Code before the unique code
Code Unique to First Derived Class
Shared Code after the unique code
Shared Code before the unique code
Code Unique to Second Derived Class
Shared Code after the unique code
Si PrintStartMessage () n'était pas une fonction virtuelle pure mais une fonction virtuelle normale, le compilateur ne s'en plaindrait pas. Cependant, vous devez toujours comprendre pourquoi la version dérivée de PrintStartMessage () n'est pas appelée.
Comme la classe dérivée appelle le constructeur de la classe de base avant son propre constructeur, la classe dérivée se comporte comme la classe de base et appelle donc la fonction de la classe de base.