如何在 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(),不需要做任何其他更改。

但是,我希望 D具有与 B相同的构造函数集,包括复制建构子和赋值运算符:

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

我必须在 D中重写它们吗? 还是有办法使用 B的构造函数和运算符?我尤其希望避免重写赋值运算符,因为它必须访问所有 B的私有成员变量。

83980 次浏览

Short Answer: Yes you will need to repeat the work in D

Long answer:

If your derived class 'D' contains no new member variables then the default versions (generated by the compiler should work just fine). The default Copy constructor will call the parent copy constructor and the default assignment operator will call the parent assignment operator.

But if your class 'D' contains resources then you will need to do some work.

I find your copy constructor a bit strange:

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


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

Normally copy constructors chain so that they are copy constructed from the base up. Here because you are calling the assignment operator the copy constructor must call the default constructor to default initialize the object from the bottom up first. Then you go down again using the assignment operator. This seems rather inefficient.

Now if you do an assignment you are copying from the bottom up (or top down) but it seems hard for you to do that and provide a strong exception guarantee. If at any point a resource fails to copy and you throw an exception the object will be in an indeterminate state (which is a bad thing).

Normally I have seen it done the other way around.
The assignment operator is defined in terms of the copy constructor and swap. This is because it makes it easier to provide the strong exception guarantee. I don't think you will be able to provide the strong guarantee by doing it this way around (I could be wrong).

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

Even if you derive a class D from from X this does not affect this pattern.
Admittedly you need to repeat a bit of the work by making explicit calls into the base class, but this is relatively 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 */
}
};

You most likely have a flaw in your design (hint: slicing, entity semantics vs value semantics). Having a full copy/value semantics on an object from a polymorphic hierarchy is often not a need at all. If you want to provide it just in case one may need it later, it means you'll never need it. Make the base class non copyable instead (by inheriting from boost::noncopyable for instance), and that's all.

The only correct solutions when such need really appears are the envelop-letter idiom, or the little framework from the article on Regular Objects by Sean Parent and Alexander Stepanov IIRC. All the other solutions will give you trouble with slicing, and/or the LSP.

On the subject, see also C++CoreReference C.67: C.67: A base class should suppress copying, and provide a virtual clone instead if "copying" is desired.

You will have to redefine all constructors that are not default or copy constructors. You do not need to redefine the copy constructor nor assignment operator as those provided by the compiler (according to the standard) will call all the base's versions:

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

Note that, as sbi noted, if you define any constructor the compiler will not generate the default constructor for you and that includes the copy constructor.

You can explicitly call constructors and assignment operators:

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

The interesting thing is that this works even if you didn't explicitly define these functions (it then uses the compiler generated functions).

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

The original code is wrong:

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 general, you can not define the copy constructor in terms of the copy assignment, because the copy assignment must release the resources and the copy constructor don't !!!

To understand this, consider:

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

To avoid memory leak , the copy assignment first MUST delete the memory pointed by 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 !!!
}

So, copy constructor and copy assignment are different because the former construct and object into an initialized memory and, the later, MUST first release the existing memory before constructing the new object.

If you do what is originally suggested in this article:

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

you will be deleting an unexisting memory.