3

It is to my understanding that in C++, derived classes do not inherit overloaded assignment operators from base classes. I have written an example below where I explicitly overload the assignment operator for both a base class and a derived class. In the sample output at the bottom, there is a section:

Index:1 - Base Value:1
Index:1 - Derived Value:2
Index:2 - Base Value:2
Index:2 - Derived Value:2

The intended output should have been:

Index:1 - Base Value:2
Index:1 - Derived Value:2
Index:2 - Base Value:2
Index:2 - Derived Value:2 

This output was not unexpected. I realize that the assignment operator isn't inherited. Is there a de facto safe/standard approach to having a derived class call the base class's assignment operator? My best guess so far is to have the derived class's assignment operator function up-cast the derived class object to a base class object and assign the RHS to the LHS. Is this a generally-accepted-as-safe approach?

If I use the "Proposed Solution" (bottom of page), it works, but only because none of the operators are declared as virtual. If I up-cast and use a non-virtual function, the version of the function used depends on the cast, while if I use a virtual function, the member function used depends on the actual type of the object rather than what it is being cast as.

Thank you.

Code Listing


/*******************************************************************************
 * Preprocessor Directives
 ******************************************************************************/
#include <iostream>
using namespace std;


/*******************************************************************************
 * Class Declarations and Function Prototypes
 ******************************************************************************/
class Base {
   private:
   protected:
   public:
      int iBInt;
      Base();        /* Constructor */
      Base(int a);   /* Constructor - Set const member */
      Base & operator=(const Base& rhs);
      virtual ~Base();
};

class Derived : public Base {
   private:
   protected:
   public:
      int iDInt;
      Derived();        /* Constructor */
      Derived(int a);   /* Constructor - Set const member */
      Derived & operator=(const Derived& rhs);
      ~Derived();
};

/*******************************************************************************
 * Class and Function Definitions
 ******************************************************************************/
/******************************************************************************/
Base::Base(void) {
   cout << __FUNCTION__ << endl;
   iBInt = 0;
   cout << "iBInt: " << iBInt << endl;
}

/******************************************************************************/
Base::Base(int a) {
   cout << __FUNCTION__ << endl;
   iBInt = a;
   cout << "iBInt: " << iBInt << endl;
}

/******************************************************************************/
Base::~Base(void) {
   cout << __FUNCTION__ << endl;
}

/******************************************************************************/
Base& Base::operator=(const Base& rhs) {
   cout << "Base::" << __FUNCTION__ << endl;
   if (this == &rhs) {
      return *this;
   }
   iBInt = rhs.iBInt;
   cout << "iBInt: " << iBInt << endl;
   return *this;
}

/******************************************************************************/
Derived::Derived(void) {
   cout << __FUNCTION__ << endl;
   iDInt = 0;
   cout << "iDInt: " << iDInt << endl;
}

/******************************************************************************/
Derived::Derived(int a) : Base(a) {
   cout << __FUNCTION__ << endl;
   iDInt = a;
   cout << "iDInt: " << iDInt << endl;
}

/******************************************************************************/
Derived::~Derived(void) {
   cout << __FUNCTION__ << endl;
}

/******************************************************************************/
Derived& Derived::operator=(const Derived& rhs) {
   cout << "Derived::" << __FUNCTION__ << endl;
   if (this == &rhs) {
      return *this;
   }
   iDInt = rhs.iDInt;
   cout << "iDInt: " << iDInt << endl;
   return *this;
}


/*******************************************************************************
 * Main Entry Point
 ******************************************************************************/
int main(void) {
   int count = 3;
   /* Generate objects */
   Derived **bArr = new Derived*[count];
   for (int i=0; i<count; i++) {
      bArr[i] = new Derived(i);
   }

   /* Set some values via overloaded assignment operator, and print out
    * updated values.
    */
   for (int i=0; i<count; i++) {
      cout << "Index:" << i << " - Base Value:" << bArr[i]->iBInt << endl;
      cout << "Index:" << i << " - Derived Value:" << bArr[i]->iDInt << endl;
   }
   *bArr[1] = *bArr[2];
   for (int i=0; i<count; i++) {
      cout << "Index:" << i << " - Base Value:" << bArr[i]->iBInt << endl;
      cout << "Index:" << i << " - Derived Value:" << bArr[i]->iDInt << endl;
   }
   /* Cleanup */
   for (int i=0; i<count; i++) {
      delete bArr[i];
   }
   delete [] bArr;

   return 0;
}

Sample Output


Base
iBInt: 0
Derived
iDInt: 0
Base
iBInt: 1
Derived
iDInt: 1
Base
iBInt: 2
Derived
iDInt: 2
Index:0 - Base Value:0
Index:0 - Derived Value:0
Index:1 - Base Value:1
Index:1 - Derived Value:1
Index:2 - Base Value:2
Index:2 - Derived Value:2
Derived::operator=
iDInt: 2
Index:0 - Base Value:0
Index:0 - Derived Value:0
Index:1 - Base Value:1
Index:1 - Derived Value:2
Index:2 - Base Value:2
Index:2 - Derived Value:2
~Derived
~Base
~Derived
~Base
~Derived
~Base

Proposed Solution/Edit


/******************************************************************************/
Derived& Derived::operator=(const Derived& rhs) {
   cout << "Derived::" << __FUNCTION__ << endl;
   if (this == &rhs) {
      return *this;
   }
   Base* b1 = this;
   Base* b2 = (Base*)&rhs;
   *b1 = *b2;
   iDInt = rhs.iDInt;
   cout << "iDInt: " << iDInt << endl;
   return *this;
}
Cloud
  • 18,753
  • 15
  • 79
  • 153

2 Answers2

7

I'd say just call the Base assignment operator directly:

Base::operator =(rhs);

Invoking the inherited class' assignment operator this way is much more clean and direct than invoking it with pointer gymnastics.


An alternate way to do this would be:

static_cast<Base &>(*this) = rhs;

This is more clean than calling the operator using pointers, but is still (IMO) less readable than calling the base operator overload explicitly.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Are there any "gotcha's" I should keep in mind when using this (ie: should I override a Base-from-Base, eg: `Base & Base::Base(const Base &rhs)` constructor) to ensure this is always used? I'm trying to limit the number of ways assignment/initialization occurs, and make sure all of them use a common interface. – Cloud Sep 02 '14 at 18:08
  • 2
    @Dogbert A copy constructor is not required for this technique; it explicitly calls the assignment operator of the base class. If that wasn't your question then I'm not sure what you are asking. – cdhowie Sep 02 '14 at 18:09
  • That covers all my bases. Thanks! – Cloud Sep 02 '14 at 18:11
  • I should correct myself: With respect to the question here (http://stackoverflow.com/questions/3278625/when-do-we-have-to-use-copy-constructors) it seems the compiler automatically generates a primitive copy constructor. Should I explicitly declare/define a copy constructor and force it to use the assignment operator I've written? I'm concerned about a non-explicit copy constructor being used in place of my assignment operator, eg: `Derived d1 = Derived(a)`, when I compile and test it, appears to use the default copy constructor and not my overloaded assignment operator. – Cloud Sep 02 '14 at 18:14
  • 2
    @Dogbert Follow the [rule of three](http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)) -- if you need to define a custom destructor, copy constructor, or assignment operator then you need to define all three. Generally you shouldn't define *any* of them if the compiler-generated default implementations would work. (In the sample code you have given they would work just fine, though I do understand that this is a contrived exercise.) – cdhowie Sep 02 '14 at 18:17
  • 2
    `Derived d1 = Derived(a)` will never use the assignment operator, it will always use the copy constructor. (If you delete the copy constructor, this line would fail to compile: `error: use of deleted function ‘Derived::Derived(const Derived&)’`.) This is true even if you provide a no-argument constructor and an assignment operator! – cdhowie Sep 02 '14 at 18:19
  • 2
    I should note that most compilers would elide the copy in `Derived d1 = Derived(a)`. However they still require a visible copy constructor, since elision is an optimization -- you don't get to violate the spec because you have access to an optimizing compiler. – cdhowie Sep 02 '14 at 18:23
2

Is there a de facto safe/standard approach to having a derived class call the base class's assignment operator?

Assignment operators (and operators in general) almost always imply value semantics. It makes sense to assign to a string, a vector, a map, a date or an int.

Virtual functions almost always imply the exact opposite. It does not make sense to assign to a stream, a window or a database implementation. Almost any class that uses virtual functions should disable copying and copy assignment.

All the problems you are experiencing are caused by the apparent desire to mix things which are hard to mix and which usually should not be mixed at all.

I would advise you to disable copying in the base class and rename the assignment operator to a regular member function:

class Base : boost::noncopyable { // in C++11, use `= delete`
   public:
      int iBInt;
      Base();        /* Constructor */
      Base(int a);   /* Constructor - Set const member */
      virtual ~Base();
      virtual void Assign(Base const &other);
};

You will almost certainly find a more descriptive name than Assign, too, and chances are you will very likely find that it would be outright wrong to call the function "Assign" in the first place.

In fact, now that the function has a normal name, you can reevaluate your entire class design without being "distracted" by the operator syntax.

Possible concerns:

  • In object-oriented programming, it is usually considered bad style to override a function just to call the parent implementation, because it means that the base class does not sufficiently establish an invariant.

  • Taking a base-class argument in a base-class virtual function can lead to very complex designs, because you are effectively trying to make the function virtual with respect to two arguments. Both the "invisible" this and the other could be of any class in the hierarchy. Could it be that four different things need to be done for any of the four combinations of Base and Derived? And what if (what should be expected) even more derived classes enter the game? And does object-oriented programming here only serve to sprinkle similar implementations across the source code rather than centralising them in one place?

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • Is it possible to disable the assignment operator, or would you simply override it and have it do nothing (ie: empty definition body). – Cloud Sep 02 '14 at 18:32
  • 1
    @Dogbert: You should disable it, along with the copy constructor. Pre-C++11, you do that by declaring them private and not implementing them (or you just use `boost::noncopyable`). In C++11, you disable the two via `= delete;`. – Christian Hackl Sep 02 '14 at 18:35