How to use base class constructors and assignment operator in C ++?

I have a class B with a set of constructors and an assignment operator.

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 } 

I want to create an inheriting class D that simply overrides the foo () function and no other changes are needed.

But I want D to have the same set of constructors, including a copy constructor and an assignment operator, like B:

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

Do I need to rewrite all of them in D, or is there a way to use the constructors and operator B? I would especially like to avoid rewriting the assignment operator because it needs to access all the variables of private member B.

+71
c ++ inheritance assignment-operator constructor
Aug 04 '09 at 9:58
source share
5 answers

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

Interestingly, this works even if you do not explicitly define these functions (then it uses the functions generated by the compiler).

 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; } }; 
+88
Aug 04 '09 at 11:32
source share

Short answer: Yes, you will need to repeat work in D

Long answer:

If your derived class 'D' does not contain new member variables, then the default versions (generated by the compiler should work just fine). The default copy constructor will invoke the parent copy constructor, and the default assignment operator will invoke the parent assignment.

But if your class โ€œDโ€ contains resources, 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;} 

Usually copy the chain of constructors so that they are copied from the base up. Here, since you invoke the assignment operator, the copy constructor must invoke the default constructor, which by default initializes the object first from the bottom up. Then you go down again with the assignment operator. It seems rather inefficient.

Now, if you are doing the job, you are copying from bottom to top (or top to bottom), but it is difficult for you to do this and provide a reliable guarantee of exclusion. If at any moment the resource cannot copy and you selected an exception, the object will be in an undefined state (which is bad).

Usually I saw how it was done the other way around.
The assignment operator is defined in terms of the copy and swap constructor. This is because it makes it easier to provide a reliable guarantee of exclusion. I do not think that you can provide a strong guarantee by doing so (I may 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 get class D from X, this does not affect this pattern.
Admittedly, you need to repeat the work a bit by making explicit calls to 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 copy constructor // Do most of D 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 */ } }; 
+16
Aug 04 '09 at 10:39
source share

You most likely have a flaw in your design (hint: slice, semantics of semantics and semantics of meanings). Having full copy / value semantics for an object from a polymorphic hierarchy is often not a necessity at all. If you want to provide it in case you need it later, then you do not need it. Make the base class incompatible instead (inheriting from boost :: noncopyable, for example), that's all.

The only right decisions when the need really arises is the letter envelope idiom or a small frame from an article on regular objects by Sean Rotern and Alexander Stepanov IIRC. All other solutions will give you problems with slicing and / or LSP.

+4
Aug 04 '09 at 10:43
source share

You will have to override all constructors that are not default constructors or copies. You do not need to override the copy constructor and assignment operator, since those provided by the compiler (in accordance with the standard) will call all the basic 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 a default constructor for you, and it includes a copy constructor.

+1
Aug 04 '09 at 11:16
source share

The source code is incorrect:

 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 cannot define a copy constructor in terms of copy assignment, because copy assignment should free up resources, and copy constructor should not.

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 leaks, assigning a copy MUST first delete the memory marked with 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 !!! } 

Thus, the copy constructor and copy destination are different because the old design and the object are in initialized memory and, later, MUST first free the existing memory before constructing a new object.

If you do what was originally proposed in this article:

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

you delete unused memory.

+1
Jan 01 '15 at 15:48
source share



All Articles