How to use base class's constructors and assignment operator in C++?
Asked Answered
W

5

115

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.

Wrench answered 4/8, 2009 at 9:58 Comment(1)
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://mcmap.net/q/189732/-inheriting-copy-and-move-constructors-of-base-class-using-quot-using-quot-keyword/5447906Lukasz
O
149

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;
    }
};  
Olein answered 4/8, 2009 at 11:32 Comment(9)
What does this mean? Base(const Base&)Otway
@CravingSpirit it's a copy constructor (with the argument name omitted).Olein
Thanks. Why do we need a copy constructor if there is already a operator= overloading?Otway
@CravingSpirit they are used in different situations, this is basic C++ I suggest you read a bit more about it.Olein
@Otway copy constructor is used for initialization, while assignment operator used in assignment expression.Orgasm
@Olein 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 .Forequarter
@Olein 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 ?Forequarter
Is there any need to use std::forward() when passing to the Base::operator=()?Rigorism
@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 en.cppreference.com/w/cpp/utility/forward)Olein
O
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 */
    }
};
Outdate answered 4/8, 2009 at 10:39 Comment(18)
+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.Chlorothiazide
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...Lubricous
Also technically you are not allowed to add stuff to the standard namespace.Outdate
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.Lubricous
@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++11Forequarter
@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.Forequarter
A resource: Something you get but have to (should) explicitly give back. Examples: Memory / File Descriptors / Open Connections / Locks etc.Outdate
@AbhishekMane I don't understand your question.Outdate
@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.Forequarter
@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"Outdate
@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.Forequarter
@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.Outdate
@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 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.Forequarter
@AbhishekMane: gist.github.com/Loki-Astari/456ba81de186f923d709129f2968bb11Outdate
@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 ?Forequarter
@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://mcmap.net/q/15703/-what-is-the-rule-of-three 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.Forequarter
@MartinYork please do replyForequarter
@MartinYork please do reply, I am still struggling with above comment. your one reply may clarify my doubt. Humble request please answer.Forequarter
S
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.

Skindeep answered 4/8, 2009 at 10:43 Comment(0)
V
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.

Vassalize answered 4/8, 2009 at 11:16 Comment(2)
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.Stanfill
@DavidRodriguez-dribeas as sbi noted, if you define any constructor the compiler it's not any constructor but any copy constructorForequarter
B
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.

Balancer answered 1/1, 2015 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.