110

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

Here it is:

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 will just override the function foo(), and no other change is required.

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

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

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

Azeem
  • 11,148
  • 4
  • 27
  • 40
Igor
  • 26,650
  • 27
  • 89
  • 114
  • If you want to just override the `foo` method, you can use `using B::operator=;` to inherit assignment operator, but copy and move constructors cannot be inherited: https://stackoverflow.com/q/49045026/5447906 – anton_rh Mar 01 '18 at 07:55

5 Answers5

147

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;
    }
};  
Motti
  • 110,860
  • 49
  • 189
  • 262
  • What does this mean? `Base(const Base&)` – qed Jul 12 '13 at 16:43
  • 2
    @CravingSpirit it's a [copy constructor](http://en.wikipedia.org/wiki/Copy_constructor) (with the argument name omitted). – Motti Jul 13 '13 at 20:37
  • Thanks. Why do we need a copy constructor if there is already a operator= overloading? – qed Jul 14 '13 at 10:54
  • 2
    @CravingSpirit they are used in different situations, this is basic C++ I suggest you read a bit more about it. – Motti Jul 14 '13 at 12:53
  • 1
    @qed copy constructor is used for initialization, while assignment operator used in assignment expression. – Bin Apr 19 '16 at 07:54
  • @Motti __1.__ we can call constructor of base class explicitly through any member function of derived class like `base::base()` and what happen exactly is temporary object created and destroyed as that statement ends. similarly overloaded=operator __2.__ but we can't call copy constructor explicitly through member function of derived class we can just call indirectly by copying one base object to another in member function like `base b2=b1` if I try to do something like `base::base(b1)` it gives error . – Abhishek Mane Jul 06 '21 at 07:40
  • @Motti __3.__ Also destructor we can't call using membership label in member functions we have to use object. so am I right in all above 3 ? – Abhishek Mane Jul 06 '21 at 07:40
  • Is there any need to use std::forward() when passing to the Base::operator=()? – Mackie Messer Jul 24 '23 at 19:19
  • 1
    @MackieMesser there's only need to use `std::forward()` when dealing with templates (specifically forwarding references) which is not the case here. In any case, this answer is from 2009, before C++11 and `std::forward()` came out. (see https://en.cppreference.com/w/cpp/utility/forward) – Motti Jul 25 '23 at 06:41
20

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 */
    }
};
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • +1. Since you are at it, add an specialization to std::swap for your type that delegates on your swap member method: 'namespace std { template<> void std::swap( D & lhs, D & rhs ) { lhs.swap(rhs); } }' This way the specialized swap operation can be used in STL algorithms. – David Rodríguez - dribeas Aug 04 '09 at 19:55
  • 1
    Adding a free swap function in the same namespace as X *should* have the same effect (via ADL), but someone was saying recently that MSVC incorrectly calls std::swap explicitly, thus making dribeas right... – Steve Jessop Aug 04 '09 at 20:40
  • Also technically you are not allowed to add stuff to the standard namespace. – Martin York Aug 05 '09 at 05:03
  • 1
    You are allowed to specialize standard algorithms in std for user-defined types. dribeas' code is valid, it's just that the gurus seem to recommend the ADL solution. – Steve Jessop Aug 05 '09 at 12:44
  • @MartinYork resource means normal variables or dynamic memory ? __1__ neither created `normal variable` nor a `dynamic memory`. you are right, we don't have to create copy constructor `(CC)` or overloaded=operator `(O=O)` h in __derived class__ , default compiler generated `(CC)` and `(O=O)` of __derived class__ calls respective `(CC)` and `(O=O)` of __base class__ . __2__ created `normal variable` in __derived class__ , it gives same result. __3__ I created `dynamic memory` `int *p=new int[4]` in __derived class__ it gives warning in `iso C++` but it works fine in `iso C++11` – Abhishek Mane May 11 '21 at 17:39
  • @MartinYork so what you told not works for `iso C++11` means I don't need to do explicit calls. Can you please explain ? I checked this all practically. – Abhishek Mane May 11 '21 at 17:48
  • 1
    A resource: Something you get but have to (should) explicitly give back. Examples: Memory / File Descriptors / Open Connections / Locks etc. – Martin York May 11 '21 at 21:31
  • @AbhishekMane I don't understand your question. – Martin York May 11 '21 at 21:32
  • @MartinYork __" But if your class 'D' contains resources then you will need to do some work. "__ now this statement is seeing to be wrong to me because when I make resources in derived class still I don't need to do any work it's just work fine, as it working before containing resources. – Abhishek Mane May 12 '21 at 04:09
  • 1
    @AbhishekMane If your class contains a resource (something you need to give back). Then you need to have a destructor to give it back. If you have a destructor then default copy constructor and assignment operator will not work (you will need to do a deep copy). This is know as the **RULE OF THREE**. If you define any of (destructor CC or O=O) then you must define all three. Please search for "Rule of Three" – Martin York May 12 '21 at 04:13
  • @MartinYork I checked Rule of three, five too then Move semantics and lot more and now I came back to my main Question here. okay, can you give one example of resource because I made `int` type, `string` type, `dynamic` memory in derived class but still I don't need to do any work . derived class copy ctor, dctor, overloaded = operator automatically calls respective base class copy ctor,dctor and overloaded= operator. – Abhishek Mane May 31 '21 at 17:48
  • 1
    @AbhishekMane Resource Example: Dynamically allocated memory: `new int(5)`; The type `int` is **not a resource**. The type `std::string` is **not a resource**; though it may dynamically allocate memory internally but that is private to the class (you don't know or need to know). The class `std::string` has implemented the appropriate CC O=O Destructor etc so it handles that all automatically and transparently to you. You can treat it like a simple object (like an int) because it has implemented the rule of five correctly. – Martin York May 31 '21 at 19:12
  • @MartinYork I created dynamic memory with `int *p=new int[4];` but derived class cc, o=o and dctor calling there respective cc, o=o and dctor in base class I don't need to do any work https://ideone.com/2lpYlr this is code and output. I didn't know how to link there but I checked on my ide it's working fine. But you said if your derived class containing some resources then you have to do some work. – Abhishek Mane Jun 01 '21 at 04:21
  • 1
    @AbhishekMane: https://gist.github.com/Loki-Astari/456ba81de186f923d709129f2968bb11 – Martin York Jun 01 '21 at 23:23
  • @MartinYork Now I get all you are saying. When we have resources(dynamic memory allocation through `new` ) it's our responsibility to free them. so we have to free this just before that object lifetime ends otherwise after destruction of object memory is going to leak. so we are doing that by using `delete` keyword in destructor. and you are talking about this work we have to do when we have resources right ? – Abhishek Mane Jun 02 '21 at 09:00
  • @MartinYork But I have one Question why memory will not leak in __derWithRuleOfThree__ but leak in __derWithDestructorOnly__. As ultimately in __derWithRuleOfThree__ we are copying __derObjectOne__ to __derObjectTwo__ . so `ptr variable p` in __derObjectTwo__ get address of variable which pointed by `ptr varible p` of __derObjectOne__ . so it's obvious that memory will leak. https://stackoverflow.com/a/4172724/11862989 in his answer fredoverflow(user with 237k Rep) said regarding __Rule of three__ `Unfortunately, this "rule" is not enforced by the C++ standard or any compiler I am aware of.` – Abhishek Mane Jun 02 '21 at 09:02
  • @MartinYork please do reply – Abhishek Mane Jun 04 '21 at 09:33
  • @MartinYork please do reply, I am still struggling with above comment. your one reply may clarify my doubt. Humble request please answer. – Abhishek Mane Jul 06 '21 at 08:14
3

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.

Luc Hermitte
  • 31,979
  • 7
  • 69
  • 83
2

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 1
    Note that the compiler won't provide a default ctor if any other ctor (this includes the copy ctor) is defined. So if you want `derived` to have a default ctor, you'll need to explicitly define one. – sbi Aug 04 '09 at 12:48
  • @DavidRodriguez-dribeas `as sbi noted, if you define any constructor the compiler` it's not __any constructor__ but __any copy constructor__ – Abhishek Mane May 10 '21 at 18:58
0

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.

Mat
  • 202,337
  • 40
  • 393
  • 406