Frage

Ich habe eine Klasse B mit einem Satz von Konstrukteuren und einem Zuweisungsoperator.

Hier ist sie:

class B
{
 public:
  B();
  B(const string& s);
  B(const B& b) { (*this) = b; }
  B& operator=(const B & b);

 private:
  virtual void foo();
  // and other private member variables and functions
};

Ich möchte eine Klasse inheriting D erstellen, die nur die Funktion foo() außer Kraft setzen, und keine andere Änderung erforderlich ist.

Aber, ich will D den gleichen Satz von Konstrukteuren hat, einschließlich Copykonstruktor und Zuweisungsoperator als B:

D(const D& d) { (*this) = d; }
D& operator=(const D& d);

Muss ich alle von ihnen in D umschreiben, oder ist es eine Möglichkeit, B der Konstrukteure und Betreiber zu bedienen? Ich würde vor allem den Zuweisungsoperator vermeiden wollen Umschreiben weil es alle B privaten Membervariablen zuzugreifen.

War es hilfreich?

Lösung

Sie können explizit Konstrukteuren anrufen und Zuweisungsoperatoren:

class Base {
//...
public:
    Base(const Base&) { /*...*/ }
    Base& operator=(const Base&) { /*...*/ }
};

class Derived : public Base
{
    int additional_;
public:
    Derived(const Derived& d)
        : Base(d) // dispatch to base copy constructor
        , additional_(d.additional_)
    {
    }

    Derived& operator=(const Derived& d)
    {
        Base::operator=(d);
        additional_ = d.additional_;
        return *this;
    }
};

Das Interessante daran ist, dass dies funktioniert, auch wenn Sie nicht explizit diese Funktionen definiert haben (es verwendet dann die Compiler erzeugten Funktionen).

class ImplicitBase { 
    int value_; 
    // No operator=() defined
};

class Derived : public ImplicitBase {
    const char* name_;
public:
    Derived& operator=(const Derived& d)
    {
         ImplicitBase::operator=(d); // Call compiler generated operator=
         name_ = strdup(d.name_);
         return *this;
    }
};  

Andere Tipps

Kurze Antwort: Ja, Sie müssen die Arbeit in D

wiederholen

Lange Antwort:

Wenn Ihre abgeleitete Klasse ‚D‘ enthält keine neuen Elementvariablen dann die Standardversionen (vom Compiler generiert sollte gut funktionieren). Der Standard-Copy-Konstruktor wird die übergeordnete Copykonstruktor und die Standard-Zuweisungsoperator rufen die Eltern Zuweisungsoperator aufrufen.

Aber wenn Ihre Klasse ‚D‘ Ressourcen enthält, dann werden Sie müssen einige Arbeit tun.

Ich finde Ihr Copykonstruktor ein wenig seltsam:

B(const B& b){(*this) = b;}

D(const D& d){(*this) = d;}

kopieren Normalerweise Kette Konstrukteuren, so dass sie von der Basiskopie aufgebaut sind. Hier, weil Sie den Zuweisungsoperator rufen die Copykonstruktor müssen den Standard-Konstruktor rufen Sie das Objekt von unten auf die Standard initialisieren oben zuerst. Dann gehen Sie wieder nach unten den Zuweisungsoperator. Dies scheint eher ineffizient.

Nun, wenn Sie einen Auftrag tun Sie von unten zu kopieren sind (oder von oben nach unten), aber es scheint schwer für Sie, das zu tun und eine starke Ausnahme Garantie. Wenn zu irgendeinem Zeitpunkt eine Ressource kopieren fehlschlägt und Sie werfen eine Ausnahme wird das Objekt in einem unbestimmten Zustand sein (das ist eine schlechte Sache ist).

Normalerweise um Ich habe es in die andere Richtung getan gesehen.
Der Zuweisungsoperator ist in Bezug auf den Kopierkonstruktor und Swap definiert. Dies liegt daran, es einfacher zu schaffen, die starke Ausnahme Garantie macht. Ich glaube nicht, Sie in der Lage sein werden, durch die starke Garantie bietet es auf diese Weise um zu tun (ich könnte falsch sein).

class X
{
    // If your class has no resources then use the default version.
    // Dynamically allocated memory is a resource.
    // If any members have a constructor that throws then you will need to
    // write your owen version of these to make it exception safe.


    X(X const& copy)
      // Do most of the work here in the initializer list
    { /* Do some Work Here */}

    X& operator=(X const& copy)
    {
        X tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(X& s) throws()
    {
        /* Swap all members */
    }
};

Auch wenn Sie eine Klasse D von von X leitet sich das dieses Muster nicht beeinflussen.
Zwar müssen Sie, indem sie explizite Anrufe in die Basisklasse ein wenig von der Arbeit wiederholen, aber das ist relativ trivial.

class D: public X
{

    // Note:
    // If D contains no members and only a new version of foo()
    // Then the default version of these will work fine.

    D(D const& copy)
      :X(copy)  // Chain X's copy constructor
      // Do most of D's work here in the initializer list
    { /* More here */}



    D& operator=(D const& copy)
    {
        D tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(D& s) throws()
    {
        X::swap(s); // swap the base class members
        /* Swap all D members */
    }
};

Sie höchstwahrscheinlich einen Fehler in Ihrem Design haben (Tipp: Slicing Einheit Semantik vs Wert Semantik ). Eine vollständige Kopie / Wert Semantik auf einem Objekt aus einer polymorphen Hierarchie zu haben, ist oft überhaupt nicht notwendig. Wenn Sie es nur für den Fall zur Verfügung stellen möchten kann man es später brauchen, bedeutet das, Sie nie brauchen werden. Sprechen Sie die Basisklasse nicht kopierbar statt (durch Erben von boost :: noncopyable zum Beispiel), und das ist alles.

Die einzig richtige Lösung, wenn eine solche Notwendigkeit, wirklich erscheint sind die umhüllen Buchstaben Idiom , oder der kleine Rahmen aus dem Artikel auf Regelmäßige Objekte von Sean Eltern und Alexander Stepanov IIRC. Alle anderen Lösungen geben Ihnen Probleme mit Schneiden, und / oder die LSP.

Zum Thema siehe auch C ++ CoreReference C.67: C.67. eine Basisklasse Kopieren unterdrücken soll, und bietet einen virtuellen Klon statt, wenn „Kopieren“ gewünscht wird,

Sie müssen alle Konstrukteure neu zu definieren, die nicht default sind oder Kopieren Konstrukteure. Sie brauchen nicht den Kopierkonstruktor noch Zuweisungsoperator als diejenigen, die durch den Compiler (nach der Norm) zur Verfügung gestellt, um neu definieren wird alle Versionen Base nennen:

struct base
{
   base() { std::cout << "base()" << std::endl; }
   base( base const & ) { std::cout << "base(base const &)" << std::endl; }
   base& operator=( base const & ) { std::cout << "base::=" << std::endl; }
};
struct derived : public base
{
   // compiler will generate:
   // derived() : base() {}
   // derived( derived const & d ) : base( d ) {}
   // derived& operator=( derived const & rhs ) {
   //    base::operator=( rhs );
   //    return *this;
   // }
};
int main()
{
   derived d1;      // will printout base()
   derived d2 = d1; // will printout base(base const &)
   d2 = d1;         // will printout base::=
}

Beachten Sie, dass, wie sbi erwähnt, wenn Sie irgendeinen Konstruktor definieren wird der Compiler den Standardkonstruktor nicht für Sie erzeugen und das schließt die Copykonstruktor.

Der ursprüngliche Code ist falsch:

class B
{
public:
    B(const B& b){(*this) = b;} // copy constructor in function of the copy assignment
    B& operator= (const B& b); // copy assignment
 private:
// private member variables and functions
};

In der Regel können Sie nicht den Kopierkonstruktor in Bezug auf die Kopie Zuordnung definieren, weil die Kopie Zuordnung der Ressourcen und den Kopierkonstruktor freigeben müssen nicht !!!

Um dies zu verstehen, betrachten:

class B
{
public:
    B(Other& ot) : ot_p(new Other(ot)) {}
    B(const B& b) {ot_p = new  Other(*b.ot_p);}
    B& operator= (const B& b);
private:
    Other* ot_p;
};

Speicherverlust zu vermeiden, die Kopie Zuordnung zuerst den Speicher von ot_p darauf löschen muss:

B::B& operator= (const B& b)
{
    delete(ot_p); // <-- This line is the difference between copy constructor and assignment.
    ot_p = new  Other(*b.ot_p);
}
void f(Other& ot, B& b)
{
    B b1(ot); // Here b1 is constructed requesting memory with  new
    b1 = b; // The internal memory used in b1.op_t MUST be deleted first !!!
}

So, Copy-Konstruktor und Zuordnung zu kopieren sind anders, weil das ehemalige Konstrukt und das Objekt in einen initialisierten Speicher und die später, muss zuerst den vorhandenen Speicher freigeben, bevor das neue Objekt zu konstruieren.

Wenn Sie das tun, was ursprünglich in diesem Artikel vorgeschlagen wird:

B(const B& b){(*this) = b;} // copy constructor

Sie werden unexisting Speicher werden gelöscht werden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top