web-dev-qa-db-ger.com

virtueller Zuweisungsoperator C ++

Zuweisungsoperator in C++ kann virtuell gemacht werden. Warum ist es erforderlich? Können wir auch andere Betreiber virtuell machen?

65
Kazoom

Der Zuweisungsoperator muss nicht virtuell gemacht werden.

Die folgende Diskussion bezieht sich auf operator=, Sie gilt jedoch auch für jede Überladung von Operatoren, die den fraglichen Typ und jede Funktion, die den fraglichen Typ übernimmt.

Die folgende Diskussion zeigt, dass das virtuelle Schlüsselwort nichts über die Vererbung eines Parameters im Hinblick auf das Finden einer passenden Funktionssignatur weiß. Im letzten Beispiel wird gezeigt, wie die Zuweisung beim Umgang mit geerbten Typen richtig gehandhabt wird.


Virtuelle Funktionen kennen die Vererbung von Parametern nicht:

Die Signatur einer Funktion muss identisch sein, damit virtuell ins Spiel kommt. Obwohl also im folgenden Beispiel operator = virtualisiert wird, wird der Aufruf in D niemals als virtuelle Funktion ausgeführt, da sich die Parameter und der Rückgabewert von operator = unterscheiden.

Die Funktionen B::operator=(const B& right) und D::operator=(const D& right) unterscheiden sich zu 100% und werden als zwei unterschiedliche Funktionen angesehen.

class B
{
public:
  virtual B& operator=(const B& right)
  {
    x = right.x;
    return *this;
  }

  int x;

};

class D : public B
{
public:
  virtual D& operator=(const D& right)
  {
    x = right.x;
    y = right.y;
    return *this;
  }
  int y;
};

Standardwerte und 2 überladene Operatoren:

Sie können jedoch eine virtuelle Funktion definieren, mit der Sie Standardwerte für D festlegen können, wenn sie einer Variablen vom Typ B zugewiesen wird. Dies ist auch dann der Fall, wenn Ihre B-Variable wirklich ein D ist, das in einer Referenz eines B gespeichert ist D::operator=(const D& right) function.

Im folgenden Fall wird eine Zuweisung von 2D-Objekten verwendet, die in 2B-Referenzen gespeichert sind ... die Überschreibung D::operator=(const B& right).

//Use same B as above

class D : public B
{
public:
  virtual D& operator=(const D& right)
  {
    x = right.x;
    y = right.y;
    return *this;
  }


  virtual B& operator=(const B& right)
  {
    x = right.x;
    y = 13;//Default value
    return *this;
  }

  int y;
};


int main(int argc, char **argv) 
{
  D d1;
  B &b1 = d1;
  d1.x = 99;
  d1.y = 100;
  printf("d1.x d1.y %i %i\n", d1.x, d1.y);

  D d2;
  B &b2 = d2;
  b2 = b1;
  printf("d2.x d2.y %i %i\n", d2.x, d2.y);
  return 0;
}

Drucke:

d1.x d1.y 99 100
d2.x d2.y 99 13

Was zeigt, dass D::operator=(const D& right) nie verwendet wird.

Ohne das virtuelle Schlüsselwort für B::operator=(const B& right) hätten Sie die gleichen Ergebnisse wie oben, aber der Wert von y würde nicht initialisiert. Das heißt es würde die B::operator=(const B& right) verwenden


Ein letzter Schritt, um alles zusammenzubinden, RTTI:

Sie können RTTI verwenden, um virtuelle Funktionen, die Ihren Typ annehmen, ordnungsgemäß zu verarbeiten. Hier ist der letzte Teil des Puzzles, um herauszufinden, wie die Zuweisung im Umgang mit möglicherweise vererbten Typen richtig gehandhabt wird.

virtual B& operator=(const B& right)
{
  const D *pD = dynamic_cast<const D*>(&right);
  if(pD)
  {
    x = pD->x;
    y = pD->y;
  }
  else
  {
    x = right.x;
    y = 13;//default value
  }

  return *this;
}
47
Brian R. Bondy

Das hängt vom Betreiber ab.

Wenn Sie einen Zuweisungsoperator virtuell machen, können Sie ihn überschreiben, um weitere Felder zu kopieren.

Wenn Sie also eine Basis & haben und tatsächlich eine Abgeleitete & als dynamischen Typ haben und die Abgeleitete mehr Felder hat, werden die richtigen Dinge kopiert.

In diesem Fall besteht jedoch das Risiko, dass Ihre LHS eine Abgeleitete und die RHS eine Basis ist. Wenn der virtuelle Operator also Abgeleitet ausgeführt wird, ist Ihr Parameter keine Abgeleitete und Sie haben keine Möglichkeit, Felder daraus zu entfernen.

Hier ist eine gute Diskussion: http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html

24
Uri

Brian R. Bondy schrieb:


Ein letzter Schritt, um alles zusammenzubinden, RTTI:

Sie können RTTI verwenden, um virtuelle Funktionen, die Ihren Typ annehmen, ordnungsgemäß zu verarbeiten. Hier ist der letzte Teil des Puzzles, um herauszufinden, wie die Zuweisung im Umgang mit möglicherweise vererbten Typen richtig gehandhabt wird.

virtual B& operator=(const B& right)
{
  const D *pD = dynamic_cast<const D*>(&right);
  if(pD)
  {
    x = pD->x;
    y = pD->y;
  }
  else
  {
    x = right.x;
    y = 13;//default value
  }

  return *this;
}

Ich möchte zu dieser Lösung einige Bemerkungen hinzufügen. Es gibt drei Probleme, wenn der Zuweisungsoperator wie oben deklariert wird.

Der Compiler generiert einen Zuweisungsoperator, der ein const D & Argument verwendet, das nicht virtuell ist und nicht das tut, was Sie vielleicht denken.

Das zweite Problem ist der Rückgabetyp. Sie geben einen Basisverweis auf eine abgeleitete Instanz zurück. Wahrscheinlich kein großes Problem, da der Code sowieso funktioniert. Es ist jedoch besser, Referenzen entsprechend zurückzugeben.

Drittens ruft der Operator für abgeleitete Typzuweisungen keinen Basisklassenzuweisungsoperator auf (was ist, wenn private Felder vorhanden sind, die Sie kopieren möchten?). Wenn Sie den Zuweisungsoperator als virtuell deklarieren, generiert der Compiler keinen für Sie. Dies ist eher ein Nebeneffekt, wenn nicht mindestens zwei Überladungen des Zuweisungsoperators vorliegen, um das gewünschte Ergebnis zu erzielen.

In Anbetracht der Basisklasse (wie in dem von mir zitierten Beitrag):

class B
{
public:
    virtual B& operator=(const B& right)
    {
        x = right.x;
        return *this;
    }

    int x;
};

Der folgende Code vervollständigt die von mir angegebene RTTI-Lösung:

class D : public B{
public:
    // The virtual keyword is optional here because this
    // method has already been declared virtual in B class
    /* virtual */ const D& operator =(const B& b){
        // Copy fields for base class
        B::operator =(b);
        try{
            const D& d = dynamic_cast<const D&>(b);
            // Copy D fields
            y = d.y;
        }
        catch (std::bad_cast){
            // Set default values or do nothing
        }
        return *this;
    }

    // Overload the assignment operator
    // It is required to have the virtual keyword because
    // you are defining a new method. Even if other methods
    // with the same name are declared virtual it doesn't
    // make this one virtual.
    virtual const D& operator =(const D& d){
        // Copy fields from B
        B::operator =(d);
        // Copy D fields
        y = d.y;
        return *this;
    }

    int y;
};

Dies scheint eine vollständige Lösung zu sein, ist es aber nicht. Dies ist keine vollständige Lösung, da Sie, wenn Sie von D ableiten, 1 Operator = benötigen, der const B & , 1 Operator =, der const D & und ein Operator, der const D2 & nimmt. Die Schlussfolgerung ist offensichtlich: Die Anzahl der Überladungen mit operator = () entspricht der Anzahl der Superklassen + 1.

In Anbetracht dessen, dass D2 D erbt, schauen wir uns an, wie die beiden geerbten operator = () -Methoden aussehen.

class D2 : public D{
    /* virtual */ const D2& operator =(const B& b){
        D::operator =(b); // Maybe it's a D instance referenced by a B reference.
        try{
            const D2& d2 = dynamic_cast<const D2&>(b);
            // Copy D2 stuff
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }

    /* virtual */ const D2& operator =(const D& d){
        D::operator =(d);
        try{
            const D2& d2 = dynamic_cast<const D2&>(d);
            // Copy D2 stuff
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }
};

Es ist offensichtlich, dass der Operator = (const D2 &) nur Felder kopiert, stellen Sie sich vor, als ob es dort wäre. Wir können ein Muster in den geerbten operator = () -Überladungen feststellen. Leider können wir keine virtuellen Template-Methoden definieren, die sich um dieses Muster kümmern. Wir müssen mehrmals denselben Code kopieren und einfügen, um einen vollständigen polymorphen Zuweisungsoperator zu erhalten, die einzige Lösung, die ich sehe. Gilt auch für andere Binäroperatoren.


Bearbeiten

Wie in den Kommentaren erwähnt, besteht das Mindeste, was getan werden kann, um das Leben zu erleichtern, darin, den obersten Zuweisungsoperator für Oberklassen = () zu definieren und ihn von allen anderen Methoden für Oberklassenoperatoren = () aufzurufen. Auch beim Kopieren von Feldern kann eine _copy-Methode definiert werden.

class B{
public:
    // _copy() not required for base class
    virtual const B& operator =(const B& b){
        x = b.x;
        return *this;
    }

    int x;
};

// Copy method usage
class D1 : public B{
private:
    void _copy(const D1& d1){
        y = d1.y;
    }

public:
    /* virtual */ const D1& operator =(const B& b){
        B::operator =(b);
        try{
            _copy(dynamic_cast<const D1&>(b));
        }
        catch (std::bad_cast){
            // Set defaults or do nothing.
        }
        return *this;
    }

    virtual const D1& operator =(const D1& d1){
        B::operator =(d1);
        _copy(d1);
        return *this;
    }

    int y;
};

class D2 : public D1{
private:
    void _copy(const D2& d2){
        z = d2.z;
    }

public:
    // Top-most superclass operator = definition
    /* virtual */ const D2& operator =(const B& b){
        D1::operator =(b);
        try{
            _copy(dynamic_cast<const D2&>(b));
        }
        catch (std::bad_cast){
            // Set defaults or do nothing
        }
        return *this;
    }

    // Same body for other superclass arguments
    /* virtual */ const D2& operator =(const D1& d1){
        // Conversion to superclass reference
        // should not throw exception.
        // Call base operator() overload.
        return D2::operator =(dynamic_cast<const B&>(d1));
    }

    // The current class operator =()
    virtual const D2& operator =(const D2& d2){
        D1::operator =(d2);
        _copy(d2);
        return *this;
    }

    int z;
};

Eine Methode set defaults ist nicht erforderlich, da sie nur einen Aufruf erhalten würde (im Basisoperator = () Überladung). Änderungen beim Kopieren von Feldern erfolgen an einer Stelle und alle operator = () -Überladungen sind betroffen und haben ihren beabsichtigten Zweck.

Danke sehe für den Vorschlag.

6
Andrei15193

Die virtuelle Zuordnung wird in den folgenden Szenarien verwendet:

//code snippet
Class Base;
Class Child :public Base;

Child obj1 , obj2;
Base *ptr1 , *ptr2;

ptr1= &obj1;
ptr2= &obj2 ;

//Virtual Function prototypes:
Base& operator=(const Base& obj);
Child& operator=(const Child& obj);

Fall 1: obj1 = obj2;

In diesem virtuellen Konzept spielt es keine Rolle, wie wir operator= Für die Klasse Child aufrufen.

Fall 2 & 3: * ptr1 = obj2;
* ptr1 = * ptr2;

Hier wird die Zuordnung nicht wie erwartet sein. Grund dafür ist, dass operator= Stattdessen für die Klasse Base aufgerufen wird.

Es kann behoben werden mit:
1) Casting

dynamic_cast<Child&>(*ptr1) = obj2;   // *(dynamic_cast<Child*>(ptr1))=obj2;`
dynamic_cast<Child&>(*ptr1) = dynamic_cast<Child&>(*ptr2)`

2) Virtuelles Konzept

Die einfache Verwendung von virtual Base& operator=(const Base& obj) hilft nicht weiter, da sich die Signaturen in Child und Base für operator= Unterscheiden.

Wir müssen Base& operator=(const Base& obj) in der Child-Klasse zusammen mit der üblichen Child& operator=(const Child& obj)-Definition hinzufügen. Es ist wichtig, eine spätere Definition einzuschließen, da in Abwesenheit dieses Standardzuweisungsoperators aufgerufen wird (obj1=obj2 Liefert möglicherweise nicht das gewünschte Ergebnis).

Base& operator=(const Base& obj)
{
    return operator=(dynamic_cast<Child&>(const_cast<Base&>(obj)));
}

Fall 4: obj1 = * ptr2;

In diesem Fall sucht der Compiler nach operator=(Base& obj) Definition in Child, da operator= Für Child aufgerufen wird. Aber da sein nicht vorhandener und Base -Typ nicht implizit zu child heraufgestuft werden kann, erfolgt dies durch Fehler. (Casting ist wie obj1=dynamic_cast<Child&>(*ptr1); erforderlich.)

Wenn wir gemäß Fall 2 & 3 implementieren, wird dieses Szenario berücksichtigt.

Wie zu sehen ist, macht die virtuelle Zuweisung den Aufruf bei Zuweisungen mit Basisklassenzeigern/-referenzen eleganter.

Können wir auch andere Betreiber virtuell machen? Ja

5
sorv3235055

Dies ist nur erforderlich, wenn Sie sicherstellen möchten, dass von Ihrer Klasse abgeleitete Klassen alle ihre Mitglieder korrekt kopiert bekommen. Wenn Sie nichts mit Polymorphismus anfangen, brauchen Sie sich darüber keine Sorgen zu machen.

Ich kenne nichts, was Sie daran hindern würde, einen Operator zu virtualisieren, den Sie möchten - es sind nichts anderes als Methodenaufrufe in Sonderfällen.

Diese Seite liefert eine ausgezeichnete und detaillierte Beschreibung, wie all dies funktioniert.

4
sblom

Ein Operator ist eine Methode mit einer speziellen Syntax. Sie können es wie jede andere Methode behandeln ...

3
dmckee