Mehrfachvererbung + virtuelles Funktionschaos

Lesezeit: 8 Minuten

Mehrfachvererbung virtuelles Funktionschaos
schusch

Ich habe ein Diamant-Mehrfachvererbungsszenario wie folgt:

    A
  /   
 B     C
     /
    D

Der gemeinsame Elternteil A definiert eine virtuelle Funktion fn().
Können sowohl B als auch C definieren fn()?
Wenn dies der Fall ist, lautet die nächste Frage: Kann D ohne Begriffsklärung auf fn() von B und C zugreifen? Ich gehe davon aus, dass es dafür eine Syntax gibt.
Und ist es D möglich, dies zu tun, ohne genau zu wissen, wer B und C sind? B und C können durch einige andere Klassen ersetzt werden und ich möchte, dass der Code in D generisch ist.

Was ich versuche, ist, D irgendwie alle Instanzen von fn() aufzählen zu lassen, die es in seinen Vorfahren hat. Ist dies auf andere Weise als virtuelle Funktionen möglich?

Mehrfachvererbung virtuelles Funktionschaos
Johannes Schaub – litb

Es sei denn, Sie überschreiben fn wieder in D, nein es ist nicht möglich. Weil es in einem D-Objekt keinen finalen Overrider gibt: Beides C und B überschreiben A::fn. Sie haben mehrere Möglichkeiten:

  • Entweder fallen lassen C::fn oder B::fn. Dann diejenige, die immer noch überschreibt A::fn hat den letzten Overrider.
  • Platziere einen letzten Overrider in D. Dieser überschreibt dann A::fn ebenso gut wie fn in C und B.

Folgendes führt beispielsweise zu einem Kompilierzeitfehler:

#include <iostream>

class A {
public:
    virtual void fn() { }
};

class B : public virtual A {
public:
    virtual void fn() { }
};

class C : public virtual A {
public:
    virtual void fn() { }
};

// does not override fn!!
class D : public B, public C {
public:
    virtual void doit() {
        B::fn();
        C::fn();
    }
};

int main(int argc, char **argv) {
  D d;
  d.doit();
  return 0;
}

Sie können jedoch von A in C und B nichtvirtuelles ableiten, aber dann haben Sie keine Diamantvererbung mehr. Das heißt, jedes Datenelement in A erscheint zweimal in B und C, da Sie in einem D-Objekt zwei Unterobjekte der Basisklasse A haben. Ich würde Ihnen empfehlen, dieses Design zu überdenken. Versuchen Sie, solche Doppelobjekte zu eliminieren, die eine virtuelle Vererbung erfordern. Es führt oft zu solchen Konfliktsituationen.

Ein sehr ähnlicher Fall ist, wenn Sie eine bestimmte Funktion überschreiben möchten. Stellen Sie sich vor, Sie haben eine virtuelle Funktion gleichen Namens in B und C (jetzt ohne gemeinsame Basis A). Und in D möchten Sie jede Funktion überschreiben, aber jeder ein anderes Verhalten geben. Je nachdem, ob Sie die Funktion mit einem B-Zeiger oder C-Zeiger aufrufen, haben Sie das unterschiedliche Verhalten. Mehrfachvererbung Teil III von Herb Sutter beschreibt einen guten Weg, dies zu tun. Es könnte Ihnen bei der Entscheidung für Ihr Design helfen.

  • Ich denke, das ist nicht das, was Shoosh tun will. Er sagte: “Was ich zu tun versuche, ist, D irgendwie alle Instanzen von fn() aufzählen zu lassen, die es in seinen Vorfahren hat.”. Er möchte fn() in Klasse D nicht überschreiben.

    – lebendig

    5. März ’09 um 21:37 Uhr

  • Aus diesem Grund habe ich angegeben, dass es nicht möglich ist. Wenn sowohl C als auch B A::fn überschreiben, kann er D nicht definieren, ohne fn in D . zu überschreiben

    – Johannes Schaub – litb

    5. März ’09 um 21:52

  • Was meinst du mit “fallen”?

    – yanpas

    31. Dezember ’17 um 9:31

Mehrfachvererbung virtuelles Funktionschaos
lebendig

Erste Frage, ja, B und C können definieren fn() als virtuelle Funktion. Zweitens kann D natürlich zugreifen B::fn() und C::fn() durch Verwendung des Gültigkeitsbereichsoperators :: Dritte Frage: D muss mindestens B und C kennen, da Sie diese auf der Vererbungsliste definieren müssen. Sie können Vorlagen verwenden, um die Typen von B und C zu öffnen:

class A
{
public:
   virtual ~A() {}
   virtual void fn() = 0;
};

class B: public A
{
public:
   virtual ~B() {}
   virtual void fn(){ std::cout << "B::fn()" << std::endl; }
};

class C: public A
{
public:
   virtual ~C() {}
   virtual void fn(){ std::cout << "C::fn()" << std::endl; }
};

template <typename TypeB, typename TypeC>
class D: public TypeB, public TypeC
{
public:
   void Do()
   {
      static_cast<TypeB*>(this)->fn();
      static_cast<TypeC*>(this)->fn();
   }
};

typedef D<B, C> DInst;

DInst d;
d.Do();

Über den Wunsch, alle fn()-Funktionen aller Klassen, von denen D erbt, automatisch aufzuzählen: Ich bin mir nicht sicher, ob das möglich ist, ohne auf MPL zurückzugreifen. Zumindest können Sie mein obiges Beispiel um Versionen erweitern, die mit 3 und mehr Template-Parametern umgehen, aber ich denke, es gibt eine (interne Compiler-)Obergrenze der Anzahl von Klassen-Template-Parametern.

  • Dies ist wahrscheinlich die Lösung, die dem, was ich brauche, am nächsten kommt. Leider ist in meinem Fall auch die Anzahl der Klassen in der Vererbung (B,C) variabel. schätze, dies muss auf die variablen Vorlagenargumente von C++0x warten.

    – schusch

    5. März ’09 um 20:17

  • Stellen Sie sich vor, Sie haben diesen Code: A *a = some_d_object; a->fn(); welche version von fn soll heißen?

    – Johannes Schaub – litb

    5. März ’09 um 20:53

  • @vividos: Interessant. Gemäß Abschnitt 10.3.3 des C++-Standards muss jede virtuelle Funktion in einer Klasse einen eindeutigen abschließenden Overrider haben, um “wohlgeformt” zu sein – was bedeutet, dass ein Compiler eine Fehlermeldung ausgeben muss, wenn dies nicht der Fall ist. Aber jeder Compiler, den ich ausprobiert habe (MSVC++9, MinGW, g++ 4.1.2 unter Linux)…

    – j_random_hacker

    7. März ’09 um 19:51

  • … hat Ihr Code ohne Probleme kompiliert und ausgeführt. (Ich habe sogar Comeau C++ ausprobiert, eines der strengeren, und es hat auch funktioniert!) Compilerfehler wurden nur erzeugt, wenn versucht wurde, die mehrdeutige Funktion fn() tatsächlich aufzurufen. Technisch gesehen haben alle 4 Compiler Fehler …

    – j_random_hacker

    7. März ’09 um 19:55

  • @j_random_hacker “was habe ich falsch verstanden“Du hast angenommen, dass es da ist eins Basisklasse A in D (Versuchen A& = d;), also da ist eins Mitgliedsfunktion A::fn() (Versuchen d.A::fn();). Es gibt zwei Basen A (instrumentieren Sie die c|dtors, um ihre Adressen zu sehen), also zwei Mitglieder A::fn() in d: d.B::A::fn() und d.C::A::fn(). Jeder von ihnen hat tatsächlich einen letzten Overrider.

    – neugieriger Typ

    1. November ’11 um 15:13

Sie können die Definitionen von fn() in der Abstammung nicht aufzählen. C++ fehlt die Reflexion. Die einzige Möglichkeit, die ich mir vorstellen kann, ist eine riesige Schleife, die die Typ-IDs aller möglichen Vorfahren testet. Und es tut weh, sich das vorzustellen.

Vielleicht möchten Sie sich ansehen Loki TypeLists, wenn Sie wirklich in der Lage sein müssen, Vorfahren zu verfolgen und Typen aufzuzählen. Ich bin mir nicht sicher, ob das, was Sie verlangen, ohne viel Arbeit wirklich möglich ist. Stellen Sie sicher, dass Sie hier nicht übertrieben sind.

Wenn Sie MI auf diese Weise verwenden möchten (d. h., der gefürchtete Diamant), dann sollten Sie sehr genau angeben, welches virtuelle Mitglied Sie möchten. Ich kann mir keinen guten Fall vorstellen, in dem Sie die Semantik von wählen möchten B::fn() über C::fn() ohne explizit eine Entscheidung beim Schreiben zu treffen D. Sie werden wahrscheinlich eine über die andere (oder sogar beide) auswählen, je nachdem, was die einzelne Methode tut. Wenn Sie eine Entscheidung getroffen haben, ist es Voraussetzung, dass vererbte Änderungen weder die Erwartungen noch die semantische Schnittstelle ändern.

Wenn Sie sich wirklich Sorgen machen, in eine neue Klasse zu wechseln, sagen Sie E anstelle von sagen B wo E stammt nicht von B aber die gleiche Schnittstelle bietet, dann sollten Sie wirklich den Template-Ansatz verwenden, obwohl ich nicht sicher bin, warum es eine gibt static_cast<> da drin…

struct A {
    virtual ~A() {}
    virtual void f() = 0;
};
struct B: A {
    virtual void f() { std::cout << "B::f()" << std::endl; }
};
struct C: A {
    virtual void f() { std::cout << "C::f()" << std::endl; }
};

template <typename Base1, typename Base2>
struct D: Base1, Base2 {
    void g() { Base1::f(); Base2::f(); }
};

int main() {
    D<B,C> d1;
    D<C,B> d2;
    d1.g();
    d2.g();
    return 0;
}

// Outputs:
//   B::f()
//   C::f()
//   C::f()
//   B::f()

Funktioniert einwandfrei und sieht etwas übersichtlicher aus.

Vividos hat den Hauptteil des Posts bereits beantwortet. Selbst wenn ich den Bereichsoperator anstelle des umständlicheren static_cast<> + Dereferenzierungsoperators verwenden würde.

Abhängig von der vorliegenden Aufgabe können Sie möglicherweise die Vererbungsbeziehung von D auf B und C für eine weniger Kopplungszusammensetzung ändern (plus möglicherweise Vererbung von A). Dies setzt voraus, dass D nicht polimorph als B oder C verwendet werden muss und dass B und C nicht wirklich dieselbe Basisinstanz haben.

Wenn Sie sich für die Komposition entscheiden, können Sie B und C als Argumente für Ihren Konstruktor als Referenzen/Zeiger des Typs A erhalten, wodurch D die Typen B und C nicht kennt. An diesem Punkt können Sie einen Container verwenden, der so viele enthält Ein abgeleitetes Objekt. Ihre eigene Implementierung von fn() (wenn Sie dies wünschen) oder einer anderen Methode.

1641941362 901 Mehrfachvererbung virtuelles Funktionschaos
Gemeinschaft

Es gibt bereits mehrere Fragen, die sich damit beschäftigen. Es scheint, als würden uns die Fragen ausgehen. Vielleicht sollte das Suchfeld größer sein als die Schaltfläche Frage stellen.

Sehen

  • Wie kann ich den Diamanten des Todes vermeiden, wenn ich Mehrfachvererbung verwende?
  • Was ist das genaue Problem bei Mehrfachvererbung?
  • Ist Mehrfachvererbung böse?

.

395250cookie-checkMehrfachvererbung + virtuelles Funktionschaos

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy