webentwicklung-frage-antwort-db.com.de

aufruf einer reinen virtuellen Funktion vom Basisklassenkonstruktor

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?

32

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.

30
a1ex07

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>();
}
13
greatwolf

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.

Aufrufen von PrintStartMessage nach der MyBase-Konstruktion

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.

Aufrufen von PrintStartMessage vor der MyBase-Konstruktion

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.

Eine statische Funktion mit Werksfunktion

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.

Zugriff auf MyBase von PrintStartMessage aus mit CRTP

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");
    }
};

Keine Vorlagenlösung - Neugestaltung

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);
}
7
ybungalobill

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.

5
Fred Foo

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
0
Circuitrinos

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. 

0
fatma.ekici