Ich habe eine Basisklasse MyBase, die eine rein virtuelle Funktion enthält:
void PrintStartMessage() = 0
Ich möchte, dass jede abgeleitete Klasse sie in ihrem Konstruktor aufruft
dann habe ich es in den Basisklassenkonstruktor (MyBase
) geschrieben
class MyBase
{
public:
virtual void PrintStartMessage() =0;
MyBase()
{
PrintStartMessage();
}
};
class Derived:public MyBase
{
public:
void PrintStartMessage(){
}
};
void main()
{
Derived derived;
}
aber ich bekomme einen linker fehler.
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)" ([email protected]@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" ([email protected]@[email protected])
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)
Ich möchte alle abgeleiteten Klassen zwingen, ...
A- implement it
B- call it in their constructor
Wie muss ich das machen?
Es gibt viele Artikel, die erklären, warum Sie niemals virtuelle Funktionen in Konstruktor und Destruktor in C++ aufrufen sollten. Werfen Sie einen Blick auf hier und hier für Details, was während solcher Anrufe hinter den Kulissen passiert.
Kurz gesagt, Objekte werden von der Basis bis zum abgeleiteten Objekt konstruiert. Wenn Sie also versuchen, eine virtuelle Funktion vom Basisklassenkonstruktor aufzurufen, ist das Überschreiben von abgeleiteten Klassen noch nicht geschehen, da die abgeleiteten Konstruktoren noch nicht aufgerufen wurden.
Der Versuch, eine reine abstrakte Methode aus einer abgeleiteten Methode aufzurufen, während das Objekt noch erstellt wird, ist unsicher. Es ist, als würde man versuchen, Benzin in ein Auto zu füllen, aber dieses Auto befindet sich immer noch in der Fertigungslinie und der Benzintank wurde noch nicht eingesetzt.
Am ehesten können Sie so etwas tun, indem Sie Ihr Objekt zuerst vollständig konstruieren und anschließend die Methode aufrufen:
template <typename T>
T construct_and_print()
{
T obj;
obj.PrintStartMessage();
return obj;
}
int main()
{
Derived derived = construct_and_print<Derived>();
}
Sie können dies nicht so tun, wie Sie es sich vorstellen, da Sie abgeleitete virtuelle Funktionen nicht innerhalb des Basisklassenkonstruktors aufrufen können - das Objekt ist noch nicht vom abgeleiteten Typ. Aber das musst du nicht tun.
Nehmen wir an, Sie möchten so etwas tun:
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"); }
};
Die gewünschte Ablaufverfolgung wäre:
Doing MyBase initialization...
Starting Derived!!!
Dafür sind Konstrukteure da! Verschrotten Sie einfach die virtuelle Funktion und machen Sie den Konstruktor der abgeleiteten Aufgabe für die Arbeit!
class MyBase {
public:
MyBase() { printf("Doing MyBase initialization...\n"); }
};
class Derived : public MyBase {
public:
Derived() { printf("Starting Derived!!!\n"); }
};
Die Ausgabe ist gut, was wir erwarten würden:
Doing MyBase initialization...
Starting Derived!!!
Dies erzwingt jedoch nicht, dass die abgeleiteten Klassen die PrintStartMessage
-Funktionalität explizit implementieren. Überlegen Sie sich jedoch zweimal, ob es überhaupt notwendig ist, da sie sonst ohnehin immer eine leere Implementierung bieten können.
Wenn Sie, wie bereits erwähnt, PrintStartMessage
aufrufen, bevor der Derived
-Konstruktor war, können Sie dies nicht tun, da noch kein Derived
-Objekt für PrintStartMessage
zum Aufruf vorhanden ist. Es würde keinen Sinn machen, PrintStartMessage
als nicht statisches Mitglied aufzufordern, da es keinen Zugriff auf eines der Derived
-Datenelemente hätte.
Alternativ können wir es zu einem statischen Member machen:
class MyBase {
public:
MyBase() {
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase {
public:
static void PrintStartMessage() { printf("Derived specific message.\n"); }
};
Eine natürliche Frage stellt sich, wie es genannt wird?
Es gibt zwei Lösungen, die ich sehen kann: Eine ähnelt der von @greatwolf, wo Sie sie manuell aufrufen müssen. Da es sich jedoch um ein statisches Member handelt, können Sie es aufrufen, bevor eine Instanz von MyBase
erstellt wurde:
template<class T>
T print_and_construct() {
T::PrintStartMessage();
return T();
}
int main() {
Derived derived = print_and_construct<Derived>();
}
Die Ausgabe wird sein
Derived specific message.
Doing MyBase initialization...
Dieser Ansatz erzwingt, dass alle abgeleiteten Klassen PrintStartMessage
implementieren. Leider trifft dies nur zu, wenn wir sie mit unserer Factory-Funktion erstellen ... was ein großer Nachteil dieser Lösung ist.
Die zweite Lösung besteht darin, auf das neugierig wiederkehrende Vorlagenmuster (CRTP) zurückzugreifen. Indem Sie MyBase
den vollständigen Objekttyp zur Kompilierzeit mitteilen, kann er den Aufruf vom Konstruktor aus ausführen:
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"); }
};
Die Ausgabe erfolgt erwartungsgemäß, ohne dass eine spezielle Werksfunktion verwendet werden muss.
Wenn MyBase
ausgeführt wird, ist der Zugriff auf seine Mitglieder bereits zulässig. Wir können PrintStartMessage
auf die MyBase
zugreifen können, die sie aufgerufen hat:
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");
}
};
Folgendes ist auch gültig und wird sehr häufig verwendet, wenn auch etwas gefährlich:
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");
}
};
Eine weitere Möglichkeit besteht darin, Ihren Code ein wenig zu überarbeiten. IMO Dies ist eigentlich die bevorzugte Lösung, wenn Sie unbedingt eine überschriebene PrintStartMessage
innerhalb der MyBase
-Konstruktion aufrufen müssen.
Dieser Vorschlag soll Derived
von MyBase
wie folgt trennen:
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"); }
};
Sie initialisieren MyBase
wie folgt:
int main() {
Derived d;
MyBase b(&d);
}
Sie sollten in einem Konstruktor keine virtual
-Funktion aufrufen. Zeitraum . Sie müssen eine Problemumgehung finden, wie PrintStartMessage
nicht -virtual
und den Aufruf explizit in jeden Konstruktor setzen.
Ich weiß, dass dies eine alte Frage ist, aber ich bin bei der Arbeit an meinem Programm auf dieselbe Frage gestoßen.
Wenn Ihr Ziel darin besteht, die Code-Duplizierung zu reduzieren, indem die Basisklasse den gemeinsam genutzten Initialisierungscode verarbeitet, während die abgeleiteten Klassen den für sie eindeutigen Code in einer rein virtuellen Methode angeben müssen, habe ich mich dafür entschieden.
#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;
}
Die Ausgabe ist:
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
Wenn PrintStartMessage () keine rein virtuelle Funktion, sondern eine normale virtuelle Funktion ist, wird der Compiler dies nicht bemängeln. Sie müssen jedoch noch herausfinden, warum die abgeleitete Version von PrintStartMessage () nicht aufgerufen wird.
Da die abgeleitete Klasse den Konstruktor der Basisklasse vor ihrem eigenen Konstruktor aufruft, verhält sich die abgeleitete Klasse wie die Basisklasse und ruft daher die Funktion der Basisklasse auf.