Jak używać konstruktorów klasy bazowej i operatora przypisania w C++?

Mam klasę B z zestawem konstruktorów i operatorem przypisania.

Oto jest:

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
};

Chcę utworzyć klasę dziedziczącą D, która po prostu nadpisze funkcję foo() i żadna inna zmiana nie jest wymagana.

Ale chcę D mieć taki sam zestaw konstruktorów, w tym Konstruktor kopiujący i operator przypisania jak B:

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

Czy muszę przepisać wszystkie w D, Czy Jest jakiś sposób na użycie B ' s konstruktor i operator? Szczególnie chciałbym uniknąć przepisywania operatora przypisania, ponieważ musi on mieć dostęp do wszystkich prywatnych zmiennych członkowskich B.

Author: Azeem, 2009-08-04

5 answers

Można jawnie wywoływać konstruktory i operatory przypisania:

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;
    }
};

Ciekawostką jest to, że działa to nawet jeśli nie zdefiniowano jawnie tych funkcji (wtedy używa funkcji generowanych przez kompilator).

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;
    }
};  
 127
Author: Motti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-01 07:22:26

Krótka odpowiedź: Tak, musisz powtórzyć pracę w D

Długa odpowiedź:

Jeśli twoja pochodna Klasa ' D ' nie zawiera nowych zmiennych członkowskich, to domyślne wersje (generowane przez kompilator powinny działać poprawnie). Domyślny Konstruktor kopiujący wywoła nadrzędny Konstruktor kopiujący, a domyślny operator przypisania wywoła nadrzędny operator przypisania.

Ale jeśli twoja klasa " D " zawiera zasoby, będziesz musiał wykonać jakąś pracę.

I find your copy konstruktor trochę dziwny:

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

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

Zwykle kopiują konstruktory łańcuchowo tak, że są kopiowane od podstawy do góry. Tutaj, ponieważ wywołujesz operatora przypisania, Konstruktor kopiujący musi wywołać konstruktor domyślny, aby domyślnie zainicjować obiekt od dołu do góry. Następnie schodzi się ponownie za pomocą operatora przydziału. Wydaje się to raczej nieefektywne.

Teraz, jeśli wykonujesz zadanie, kopiujesz od dołu do góry (lub z góry na dół), ale wydaje ci się, że trudno ci zrób to i zapewnij silną gwarancję WYJĄTKÓW. Jeśli w dowolnym momencie zasób nie skopiuje i rzucisz wyjątek, obiekt będzie w stanie nieokreślonym(co jest złe).

Normalnie widziałem to robione na odwrót.
Operator przypisania jest zdefiniowany w kategoriach konstruktora kopiującego i swap. Dzieje się tak, ponieważ ułatwia to zapewnienie silnej gwarancji WYJĄTKÓW. Nie sądzę, że będziesz w stanie zapewnić silną gwarancję, robiąc to w ten sposób wokół (mogę się mylić).

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 */
    }
};

Nawet jeśli wywołasz klasę D Z X, to nie ma to wpływu na ten wzór.
Co prawda musisz powtórzyć trochę pracy, wykonując wyraźne wywołania do klasy bazowej, ale jest to stosunkowo trywialne.

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 */
    }
};
 17
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-04 11:19:09

Najprawdopodobniej masz wadę w swoim projekcie (podpowiedź: krojenie, semantyka encji vs semantyka wartości ). Posiadanie pełnej kopii/semantyki wartości na obiekcie z hierarchii polimorficznej często nie jest wcale potrzebne. Jeśli chcesz go dostarczyć na wypadek, gdyby ktoś potrzebował go później, oznacza to, że nigdy go nie potrzebujesz. Uczyni klasę bazową niekopiowalną (dziedzicząc na przykład z boost:: noncopyable) i to wszystko.

Jedyne poprawne rozwiązania, gdy takie potrzeby naprawdę pojawiają się envelop-letter idiom, lub mały framework z artykułu o Regular Objects autorstwa Seana Parenta i Alexandra Stepanova IIRC. Wszystkie inne rozwiązania sprawią, że będziesz miał problemy z krojeniem i / lub LSP.

Na ten temat, zobacz także C++CoreReference C. 67: C. 67: klasa bazowa powinna wstrzymać kopiowanie i zamiast tego zapewnić wirtualny klon, jeśli pożądane jest "kopiowanie" .

 4
Author: Luc Hermitte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-24 14:10:14

Będziesz musiał przedefiniować wszystkie konstruktory, które nie są domyślnymi lub Kopiuj konstruktorami. Nie trzeba redefiniowaä ‡ konstruktora kopiujÄ ... cego ani operatora przydziaĺ 'ania, poniewaĹź te dostarczone przez kompilator (zgodnie ze standardem) wywoĹ'ajÄ ... wszystkie wersje bazy:

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::=
}

Zauważ, że, jak zauważył sbi, jeśli zdefiniujesz jakiś konstruktor, kompilator nie wygeneruje dla Ciebie domyślnego konstruktora, który zawiera Konstruktor kopiujący.

 2
Author: David Rodríguez - dribeas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-04 19:45:51

Oryginalny kod jest zły:

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
};

Ogólnie rzecz biorąc, nie można zdefiniować konstruktora kopiującego pod względem przypisania kopii, ponieważ przypisanie kopii musi zwolnić zasoby ,a Konstruktor kopiujący nie!!!

Aby to zrozumieć, rozważ:

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;
};

Aby uniknąć wycieku pamięci, przypisanie kopii musi najpierw usunąć pamięć wskazywaną przez ot_p:

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 !!!
}

Więc Konstruktor kopiujący i przypisanie kopii są różne, ponieważ pierwszy konstruktor i obiekt w pamięć inicjalizowana, a następnie musi najpierw zwolnić istniejącą pamięć przed zbudowaniem nowego obiektu.

Jeśli zrobisz to, co pierwotnie zasugerowano w tym artykule:

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

Usuniesz nieużywaną pamięć.

 1
Author: Mario Galindo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-01 15:55:23