Как использовать конструкторы базового класса и оператор присваивания в C++?


у меня есть класс B С набором конструкторов и оператора присваивания.

вот это:

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

Я хочу создать класс, наследующий D это просто переопределит функцию foo(), и никаких других изменений не требуется.

но, я хочу!--3--> иметь тот же набор конструкторов, включая конструктор копирования и оператор присваивания, что и B:

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

Я должен переписать все из них в D, или есть ли способ использовать Bконструкторы и оператор? Я бы особенно хотел избежать перезаписи оператора присваивания, потому что он должен получить доступ ко всем Bчастные переменные-члены.

5 80

5 ответов:

можно явно вызывать конструкторы и операторы присваивания:

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

интересно то, что это работает, даже если вы явно не определили эти функции (затем он использует функции, созданные компилятором).

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

короткий ответ: Да вам нужно будет повторить работу в D

ответ:

Если ваш производный класс ' D ' не содержит новых переменных-членов, то версии по умолчанию (созданные компилятором должны работать нормально). Конструктор копирования по умолчанию вызовет родительский конструктор копирования, а оператор присваивания по умолчанию вызовет Родительский оператор присваивания.

но если ваш класс " D " содержит ресурсы, то вам нужно сделать некоторые работа.

Я нахожу ваш конструктор копирования немного странным:

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

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

обычно копируют цепочку конструкторов так, чтобы они были копией, построенной от основания вверх. Здесь, поскольку вы вызываете оператор присваивания, конструктор копирования должен вызвать конструктор по умолчанию,чтобы по умолчанию инициализировать объект снизу вверх. Затем вы снова спускаетесь с помощью оператора присваивания. Это кажется довольно неэффективным.

Теперь, если вы выполняете задание, которое вы копируете снизу вверх (или сверху вниз), но вам кажется трудным сделать это и обеспечить сильную гарантию исключения. Если ресурс не удается скопировать и бросить исключение, то объект будет находиться в неопределенном состоянии (что плохо).

обычно я видел, что это делается наоборот.
Оператор присваивания определяется в терминах конструктора копирования и подкачки. Это связано с тем, что это облегчает предоставление строгой гарантии исключения. Я не думаю, что вы будет в состоянии обеспечить сильную гарантию, делая это таким образом (я могу ошибаться).

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

даже если вы производите класс D от X, это не влияет на этот шаблон.
Правда, нужно повторить немного работы делать явные вызовы в базовый класс, но это относительно тривиально.

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

У вас, скорее всего, есть недостаток в вашем дизайне (подсказка: для нарезки,сущность семантики vs значение семантики). Имея полную копию/значение семантики на объект из полиморфной иерархии часто нет необходимости вообще. Если вы хотите предоставить его на всякий случай, если он может понадобиться позже, это означает, что он вам никогда не понадобится. Вместо этого сделайте базовый класс не копируемым (например, наследуя от boost::noncopyable), и это все.

единственно правильные решения, когда такие нужны действительно появляется конверт-письмо идиоматическое выражение, или маленькая структура из статьи на Обычные Объекты Шон Парент и Александр Степанов IIRC. Все остальные решения дадут вам проблемы с нарезкой и / или LSP.

по этому вопросу см. Также C++CoreReference C. 67:C. 67: базовый класс должен подавлять копирование и вместо этого предоставлять виртуальный клон, если "копирование" желательно.

вам придется переопределить все конструкторы, которые не по умолчанию или скопировать конструкторы. Вам не нужно переопределять конструктор копирования или оператор присваивания, поскольку те, которые предоставляются компилятором (в соответствии со стандартом), вызовут все версии базы:

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

обратите внимание, что, как отметил sbi, если вы определяете какой-либо конструктор, компилятор не будет генерировать конструктор по умолчанию для вас, и это включает конструктор копирования.

исходный код неверен:

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

В общем случае вы не можете определить конструктор копирования с точки зрения назначения копирования, потому что назначение копирования должно освободить ресурсы, а конструктор копирования-нет !!!

чтобы понять это, рассмотрим:

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

чтобы избежать утечки памяти, назначение копирования сначала должно удалить память, на которую указывает 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 !!!
}

Итак, конструктор копирования и назначение копирования различны, потому что прежняя конструкция и объект в инициализированную память и, позже, должны сначала освободить существующую память перед созданием нового объекта.

если вы делаете то, что первоначально было предложено в этой статье:

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

вы будете удалять несуществующую память.