2

Ich verwende virtuelle Vererbung als in dem typischen Diamanten Problem:Wie kann sichergestellt werden, dass der Zuweisungsoperator für eine virtuelle Basisklasse nur einmal aufgerufen wird?

  A 
(virtual)/ \ (virtual) 
     B  C 
      \ /
      D 

Ich bin ein Verfahrens „deep_copy_from“ in jeder Klasse mit dem Namen der Umsetzung (aber es könnte der Zuweisungsoperator =() als auch sein). Die Methode sollte die klasseneigenen Attribute kopieren und die Kopie an die obigen Klassen weitergeben.

Das Problem ist, dass die Methode A :: deep_copy_from zweimal aufgerufen wird, wenn ich eine D-Instanz tief kopiere (und sie sollte nur einmal aufgerufen werden, da es nur eine "Version" von A gibt). Was ist der beste Weg um sicherzustellen, dass es nur einmal aufgerufen wird?

(B :: deep_copy_from und C :: deep_copy_from sollte funktionieren auf die gleiche Weise).

Hier ist ein Beispielcode:

class A 
{ 
public: 
    A(string const& p_a_name) : a_name(p_a_name) { 
     cout << "A(a_name=\"" << p_a_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(A const& a) 
    { 
     cout << "A::deep_copy_from(A(a_name=\"" << a.a_name << "\"))" << endl; 
     this->a_name = a.a_name; 
    } 

protected: 
    string a_name; 
}; 

class B : public virtual A 
{ 
public: 
    B(string const &p_a_name, string const& p_b_name) : A(p_a_name), b_name(p_b_name) { 
     cout << "B(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(B const& b) 
    { 
     cout << "B::deep_copy_from(B(a_name=\"" << b.a_name << "\", b_name=\"" << b.b_name << "\"))" << endl; 
     this->A::deep_copy_from(static_cast<A const&>(b)); 
     this->b_name = b.b_name; 
    } 

protected: 
    string b_name; 
}; 

class C : public virtual A 
{ 
public: 
    C(string const &p_a_name, string const& p_c_name) : A(p_a_name), c_name(p_c_name) { 
     cout << "C(a_name=\"" << p_a_name << "\", c_name=\"" << p_c_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(C const& c) 
    { 
     cout << "C::deep_copy_from(C(a_name=\"" << c.a_name << "\", c_name=\"" << c.c_name << "\"))" << endl; 
     this->A::deep_copy_from(static_cast<A const&>(c)); 
     this->c_name = c.c_name; 
    } 

protected: 
    string c_name; 
}; 

class D : public B, public C 
{ 
public: 
    D(string const &p_a_name, string const& p_b_name, string const& p_c_name, string const& p_d_name) 
     : A(p_a_name), B(p_a_name, p_b_name), C(p_a_name, p_c_name), d_name(p_d_name) 
    { 
     cout << "D(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name 
      << "\", c_name=\"" << p_c_name << "\", d_name=\"" << p_d_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(D const& d) 
    { 
     cout << "D::deep_copy_from(D(a_name=\"" << d.a_name << "\", b_name=\"" << d.b_name 
      << "\", c_name=\"" << d.c_name << "\", d_name=\"" << d.d_name << "\"))" << endl; 
     this->B::deep_copy_from(static_cast<B const&>(d)); 
     this->C::deep_copy_from(static_cast<C const&>(d)); 
     this->d_name = d.d_name; 
    } 

protected: 
    string d_name; 
}; 

Hier ist die aktuelle Ausgabe:

A(a_name="A") 
B(a_name="A", b_name="B") 
C(a_name="A", c_name="C") 
D(a_name="A", b_name="B", c_name="C", d_name="D") 
D::deep_copy_from(D(a_name="A", b_name="B", c_name="C", d_name="D")) 
B::deep_copy_from(B(a_name="A", b_name="B")) 
A::deep_copy_from(A(a_name="A")) 
C::deep_copy_from(C(a_name="A", c_name="C")) 
A::deep_copy_from(A(a_name="A")) 

Update:

Die aktuelle Version ist jetzt:

class A 
{ 
public: 
    A(string const& p_a_name) : a_name(p_a_name) { 
     cout << "A(a_name=\"" << p_a_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(A const& a) 
    { 
     cout << "A::deep_copy_from(A(a_name=\"" << a.a_name << "\"))" << endl; 
     this->a_name = a.a_name; 
    } 

protected: 
    string a_name; 
}; 

class B : public virtual A 
{ 
public: 
    B(string const &p_a_name, string const& p_b_name) : A(p_a_name), b_name(p_b_name) { 
     cout << "B(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(B const& b) 
    { 
     cout << "B::deep_copy_from(B(a_name=\"" << b.a_name << "\", b_name=\"" << b.b_name << "\"))" << endl; 
     this->A::deep_copy_from(static_cast<A const&>(b)); 
     this->deep_copy_my_bits_from(b); 
    } 

protected: 
    void deep_copy_my_bits_from(B const& b) { 
     cout << "B::deep_copy_my_bits_from(B(a_name=\"" << b.a_name << "\", b_name=\"" << b.b_name << "\"))" << endl; 
     this->b_name = b.b_name; 
    } 

protected: 
    string b_name; 
}; 

class C : public virtual A 
{ 
public: 
    C(string const &p_a_name, string const& p_c_name) : A(p_a_name), c_name(p_c_name) { 
     cout << "C(a_name=\"" << p_a_name << "\", c_name=\"" << p_c_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(C const& c) 
    { 
     cout << "C::deep_copy_from(C(a_name=\"" << c.a_name << "\", c_name=\"" << c.c_name << "\"))" << endl; 
     this->A::deep_copy_from(static_cast<A const&>(c)); 
     this->deep_copy_my_bits_from(c); 
    } 

protected: 
    void deep_copy_my_bits_from(C const& c) { 
     cout << "C::deep_copy_my_bits_from(C(a_name=\"" << c.a_name << "\", c_name=\"" << c.c_name << "\"))" << endl; 
     this->c_name = c.c_name; 
    } 

protected: 
    string c_name; 
}; 

class D : public B, public C 
{ 
public: 
    D(string const &p_a_name, string const& p_b_name, string const& p_c_name, string const& p_d_name) 
     : A(p_a_name), B(p_a_name, p_b_name), C(p_a_name, p_c_name), d_name(p_d_name) 
    { 
     cout << "D(a_name=\"" << p_a_name << "\", b_name=\"" << p_b_name 
      << "\", c_name=\"" << p_c_name << "\", d_name=\"" << p_d_name << "\")" << endl; 
    } 

    virtual void deep_copy_from(D const& d) 
    { 
     cout << "D::deep_copy_from(D(a_name=\"" << d.a_name << "\", b_name=\"" << d.b_name 
      << "\", c_name=\"" << d.c_name << "\", d_name=\"" << d.d_name << "\"))" << endl; 
     this->A::deep_copy_from(static_cast<A const&>(d)); 
     this->B::deep_copy_my_bits_from(static_cast<B const&>(d)); 
     this->C::deep_copy_my_bits_from(static_cast<C const&>(d)); 
     this->d_name = d.d_name; 
    } 

protected: 
    string d_name; 
}; 

Und der Ausgang ist:

A(a_name="A") 
B(a_name="A", b_name="B") 
C(a_name="A", c_name="C") 
D(a_name="A", b_name="B", c_name="C", d_name="D") 
D::deep_copy_from(D(a_name="A", b_name="B", c_name="C", d_name="D")) 
A::deep_copy_from(A(a_name="A")) 
B::deep_copy_my_bits_from(B(a_name="A", b_name="B")) 
C::deep_copy_my_bits_from(C(a_name="A", c_name="C")) 

Kann ich etwas besser als das? (d. h. automatischer)

+1

Ich habe das vorher noch nie gemacht, aber ich nehme an, anstatt "Deep_copy_from" auf B und C zu nennen, könnte D stattdessen 'deep_copy_just_my_bit' auf A, B und C aufrufen. Die Sprache diktiert, dass die am meisten abgeleitete Klasse benötigt wird Um "Wissen über" virtuelle Vererbung zu haben, muss zum Beispiel der Konstruktor/Destruktor von A für D zugänglich sein, wohingegen con/Destruktoren nicht-virtueller Basen nur für die unmittelbar abgeleitete Klasse zugänglich sein müssen. Die Konstruktion hat die gleiche Eigenschaft "Mach es einmal für jede Klasse" wie du willst. Wenn die Sprache das Problem nicht ohne eine spezielle Regel lösen kann, bezweifle ich es auch. –

+1

Müssen Sie auf diese Weise Vererbung verwenden? Es kann sich lohnen, zumindest einige der Beziehungen von "ist a" zu "hat ein" umzugestalten. – Patrick

+0

@Steve, wenn Sie als Antwort anstelle eines Kommentars geantwortet haben, würde Ihr akzeptiert werden;) –

Antwort

2

@Alf ist direkt über Zuordnung: Zuordnung aufgeschraubt ist. Der Grund ist, dass es sich um eine binäre Operation handelt und es aufgrund des Kovarianzproblems nicht möglich ist, binäre Operationen in einem OO-Framework zu versenden.

Jetzt gibt es eine allgemeine Antwort auf Ihre Frage, aber zuerst gibt es zwei Dinge, die Sie wissen müssen. Die erste ist, dass virtuelle Basen immer öffentlich sind, egal was du deklarierst und egal was der Standard sagt: der Standard ist einfach falsch. [Beweis: leite einfach eine andere Klasse ab und deklariere irgendeine virtuelle Basis wieder virtuell, und du hast Zugriff]

Die zweite Tatsache ist, dass virtuelle Basen direkte Basen jeder Klasse sind, auf die sie indirekte Basen sind. Wieder ignorieren Sie den Standard, weil es falsch ist. Siehe oben.

In Anbetracht dieser beiden Tatsachen, ist es einfach, das richtige Muster zu sehen, um Doppelarbeit zu vermeiden:

Hier ist Ihr Diamant:

struct A { cp(){ "A" } virtual CP(){ cp(); } }; 
struct B : virtual A { cp(){ "B" } CP() { cp(); A::CP(); } }; 
struct C : ... ibid ... 
struct D : B, C, virtual A { 
    cp() { "D"; B::cp(); C::cp(); } 
    CP() { cp(); A::cp(); } 
}; 

I-Typen und andere Sachen der Kürze halber weggelassen Rückkehr. Die cp() -Funktion führt einen Drilldown durch, indem zuerst alle Member verarbeitet werden, die dann auf jeder nicht-virtuellen -Basis aufgerufen werden, um ihre Member (rekursiv) zu verarbeiten. Eigentlich sollte es geschützt werden, da es nicht für öffentliche Kunden ist. Die Drilldown-Funktion ist obligatorisch, da Sie nicht direkt auf indirekte nicht-virtuelle Datenbanken zugreifen können, sondern nur auf direkte.

Die CP() - Funktion ist virtuell, so dass jeder Aufruf an die vollständigen Objekte CP geht, egal auf welchen Zeiger (A, B, C oder D) Sie mit dem Diamanten zugreifen.

Es verarbeitet alle Mitglieder und nicht-virtuellen Basis subobject Mitglieder von cp() seiner eigenen Klasse aufrufen, dann ist es die virtuellen Basen verarbeitet, in diesem Fall nur ist, nämlich A.

wenn X :: CP() wird

X *X::clone() const; 

dann, wenn Sie das komplette Objekt aus jedem Zeiger klonen und die gleichen dynamischen und statischen Typen zurück: wenn Ihr dynamischer Typ B D und statischer Typ ist, werden Sie Holen Sie ein B * zu einem D-Objekt genau so, wie Sie begonnen haben.

Es ist NICHT möglich, die Zuweisung auf diese Weise durchzuführen. Es ist NICHT möglich, eine Zuweisung überhaupt durchzuführen. Der Grund dafür ist, dass die Zuweisung für zwei Argumente kovariant ist. Es gibt keine Möglichkeit, sicherzustellen, dass die Quelle und das Ziel den gleichen dynamischen Typ haben und dass die Zuweisung funktioniert. Wenn die Quelle zu groß ist, wird ein Teil davon abgeschnitten. Weitaus schlimmer, wenn das Ziel zu groß ist, werden einige davon nie zugewiesen. Daher macht es keinen Unterschied, welches Objekt (Ziel oder Quelle) Sie versenden: Es kann einfach nicht funktionieren. Die einzige Art von Zuweisung, die funktionieren kann, ist eine nicht-virtuelle, die auf dem statischen Typ basiert. Das kann auch über- oder unterschneiden, aber zumindest ist das Problem statisch evident.

Klonen funktioniert, weil es eine Funktion mit nur einem Argument ist (nämlich das Selbstobjekt). Im Allgemeinen, wenn Sie "Objektkram" im Gegensatz zu "Wertkram" verwenden, müssen Sie, da Sie Werte nur wirklich bearbeiten können, Zeiger verwenden. In diesem Fall sind clone() und Freunde genau das, was Sie wollen: Sie können einen Zeiger gut zuweisen!

0

deep_copy_from ist keine Co-Variante, Sie können nur Kovarianz in Rückgabetypen verwenden.

Sie können Warnungen mit dem Code, den Sie geschrieben haben, erhalten "Überladung verbirgt virtuelle Funktion".

Da es keine Möglichkeit gibt, die B- oder C-Version aufzurufen, ohne die A-Version aufzurufen, können Sie nicht vermeiden, dass die A-Version zweimal aufgerufen wird, wenn Sie die B- und C-Version aufrufen müssen .

Vorausgesetzt, dass B und C beide virtuell von A erben, sollten Sie sie wahrscheinlich nicht dazu bringen, die A-Version aufzurufen, da die letzte Klasse für den A-Teil verantwortlich ist.

1

Es gibt zwei Probleme: der eine über das doppelte Kopieren in den A-Teil und der zweite über die virtuelle Zuweisung op.

Virtuelle Zuweisung: keine gute Idee, weil es die Fehlererkennung von der Kompilierzeit zur Laufzeit überträgt. Einfach nicht. Die allgemeine Lösung, bei der eine virtuelle Zuweisung (oder eine ähnliche Operation wie Ihre) erforderlich zu sein scheint, besteht darin, stattdessen eine virtuelle clone Elementfunktion zu implementieren, die eine dynamisch zugewiesene Kopie erzeugt.

Doppelkopie: Die einfache Antwort ist die Zuordnung in Bezug auf die Konstruktion auszudrücken. Die idiomatische Art, dies zu tun, ist als "Swap-Idiom" bekannt. Einfach ausgedrückt, Konstrukt eine Kopie, dann tauschen Sie seinen Inhalt mit der aktuellen Instanz, dann lassen Sie Destruktor der von Ihnen konstruierten Instanz für die Bereinigung sorgen..

Prost & HTH,

+0

Ich möchte nicht Swap verwenden, da ich bereits viel vordefinierten Speicherplatz habe, also wird meine deep_copy_from diese (wann immer möglich) wiederverwenden und einfach den Inhalt überschreiben. –

Verwandte Themen