Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Eigenartiges Verhalten von überschriebenen virtuellen Methoden

8 views
Skip to first unread message

Michael Landenberger

unread,
Sep 9, 2022, 4:50:04 AM9/9/22
to
Hallo,

gegeben sei folgende Klassen-Hierarchie:

---- .h-Datei ----

//CBase

class CBase {
protected:
LPCWSTR name = nullptr;
virtual LPCWSTR getName();
public:
CBase();
}

//CDerived

class CDerived : public CBase {
protected:
LPCWSTR getName() override;
public:
CDerived();
}

---- .cpp-Datei ----

//CBase

CBase::CBase() {
name = getName();
}

LPCWSTR CBase::getName() {
return L"Basisklasse";
}

//CDerived

CDerived::CDerived() : CBase() {}

LPCWSTR CDerived::getName() {
return L"Abgeleitete Klasse";
}

-----------------------------

Wie man sieht, ruft der Konstruktor der abgeleiteten Klasse den Konstruktor
der Basisklasse auf. Dort wird die virtuelle Methode getName() aufgerufen und
deren Rückgabewert dem Member "name" zugewiesen.

Von anderen Programmiersprachen wie z. B. Delphi kenne ich es nun so, dass
auch der Konstruktor der Basisklasse *nicht* die Methode der Basisklasse
aufruft, sondern die Methode der Klasse, von der aus der Konstruktor der
Basisklasse aufgerufen wurde. D. h. wenn ich in Delphi folgendes mache:

//CBase -------------------

class CBase {
protected
name : LPCWSTR;
function getName : LPCWSTR; virtual;
public
constructor Create; virtual;
}

constructor CBase.Create;
begin
name := getName;
//ruft CDerived.getName() auf, wenn CBase.Create() von einem Objekt der
//Klasse CDerived aufgerufen wurde!
end;

function CBase.getName : LPCWSTR;
begin
Result := "Basisklasse";
end;

//CDerived ----------------

class CDerived = class(CBase) {
protected
function getName; override;
public
constructor Create; override;
}

constructor CDerived.Create;
begin
inherited Create;
end;

function CDerived.getName;
begin
Result := "Abgeleitete Klasse";
end;

und anschließend

type

derived : CDerived;

derived := CDerived.Create;

würde derived.name nach Abarbeiten des Konstruktors auf "Abgeleitete Klasse"
zeigen, und das obwohl der Member "name" im Konstruktor der Basisklasse
gesetzt wurde.

Nicht so in C++: macht man da das entsprechende

CDerived* derived = new CDerived();

zeigt derived->name auf "Basisklasse", auch wenn der Konstruktor (und damit
getName()) vom Konstruktor einer abgeleiteten Klasse aufgerufen wurde. Dieses
Verhalten irritiert mich. Wie kann ich es erreichen, dass beim Aufruf des
Konstruktors von CBase über den Konstruktor eines Objekts der Klasse CDerived
*nicht* CBase::getName(), sondern CDerived::getName() aufgerufen wird?

Gruß

Michael

Stefan Reuther

unread,
Sep 9, 2022, 2:00:07 PM9/9/22
to
Am 09.09.2022 um 00:42 schrieb Michael Landenberger:
> Wie kann ich es erreichen, dass beim Aufruf des
> Konstruktors von CBase über den Konstruktor eines Objekts der Klasse CDerived
> *nicht* CBase::getName(), sondern CDerived::getName() aufgerufen wird?

Gar nicht, da das CBase-Objekt zu dem Zeitpunkt noch nicht weiß, dass es
mal ein CDerived werden wird.

Eine Möglichkeit wäre, die Klassenstruktur derart zu gestalten, dass
CBase ein pur virtuelles Interface ohne Logik ist, und die Logik in
einer weiteren Klasse liegt:

class CBase {
public:
virtual string getName() = 0;
};
class CDerived : public CBase {
public:
string getName() override { return "abgeleitete Klasse"; }
};

class CLogic {
public:
CLogic(std::unique_ptr<CBase> pImpl)
: m_pImpl(std::move(pImpl))
{ m_name = m_pImpl->getName(); }
private:
std::unique_ptr<CBase> m_pImpl;
string m_name;
};

Dann ist das CDerived garantiert schon fertig, wenn die Konstruktion des
CLogic beginnt.


Stefan

Michael Landenberger

unread,
Sep 10, 2022, 2:50:03 AM9/10/22
to
"Stefan Reuther" schrieb am 09.09.2022 um 17:47:27:

> Am 09.09.2022 um 00:42 schrieb Michael Landenberger:
>> Wie kann ich es erreichen, dass beim Aufruf des
>> Konstruktors von CBase über den Konstruktor eines Objekts der Klasse
>> CDerived *nicht* CBase::getName(), sondern CDerived::getName()
>> aufgerufen wird?

> Gar nicht, da das CBase-Objekt zu dem Zeitpunkt noch nicht weiß, dass es
> mal ein CDerived werden wird.

Wie schafft das dann Delphi?

Gruß

Michael

paule32

unread,
Sep 10, 2022, 6:40:10 AM9/10/22
to
Am 09.09.2022 um 22:46 schrieb Michael Landenberger:

> Wie schafft das dann Delphi?

die haben für jede Klasse einen VMT (virtual methode table) Eintrag.

Stefan Reuther

unread,
Sep 10, 2022, 6:40:11 AM9/10/22
to
Am 09.09.2022 um 22:46 schrieb Michael Landenberger:
Delphi funktioniert halt anders.

In C++ setzt typischerweise der Konstruktor den vtbl-Pointer, und der
weiß natürlich nur von sich selbst ("ich bin ein CBase, also setze ich
den vtbl-Pointer für CBase").

Zumindest in Turbo Pascal, dem Urahn von Delphi, hat das nicht der
Konstruktor gemacht, sondern der, der den Konstruktor aufruft. Der weiß
natürlich auch schon, was das am Ende mal werden soll. Auch hat Turbo
Pascal nicht erzwungen, dass man irgendwo den Basisklassenkonstruktor
aufruft, wohingegen das in C++ erzwungenermaßen das Allererste ist, was
der abgeleitete Konstruktor tun muss.

Allerdings gibt es in Turbo Pascal (und m.W. in Delphi) auch keine
Mehrfachvererbung.

Du kannst das Delphi'sche
p := New(PDings, Init(foo, bar));
natürlich auch in C++ nachbauen als
p = new CDings(); // leeres Objekt mit korrektem vtbl-Zeiger
p->init(foo, bar); // Pseudo-Konstruktor
aber das ist zumindest in den meisten Coding-Styles kein idiomatisches C++.


Stefan

Markus Schaaf

unread,
Sep 10, 2022, 7:10:04 AM9/10/22
to
Am 09.09.22 um 00:42 schrieb Michael Landenberger:

> Wie man sieht, ruft der Konstruktor der abgeleiteten Klasse den Konstruktor
> der Basisklasse auf. Dort wird die virtuelle Methode getName() aufgerufen und
> deren Rückgabewert dem Member "name" zugewiesen.
>
> Von anderen Programmiersprachen wie z. B. Delphi kenne ich es nun so, dass
> auch der Konstruktor der Basisklasse *nicht* die Methode der Basisklasse
> aufruft, sondern die Methode der Klasse, von der aus der Konstruktor der
> Basisklasse aufgerufen wurde.

Der Problem wäre, dass beim Aufruf einer Derived-Funktion das
Derived-Objekt nicht erstellt wurde, aber bereits darauf
zugegriffen werden kann. Pascal umgeht das, indem es vorher alle
Variablen null-initialisiert.

Manche Bibliotheken haben Dein Problem so gelöst, dass nach
Aufruf des Konstruktors eine virtuelle Funktion aufgerufen wird.
Dazu muss man den C'tor verstecken und die Konstruktion über eine
Factory-Funktion durchführen. Andere Möglichkeit wäre, dem C'tor
der Basisklasse gleich die nötigen Daten zu übergeben, statt ihn
die aus virtuellen Aufrufen holen zu lassen.

MfG
0 new messages