Question

While playing with implementing a virtual assignment operator I have ended with a funny behavior. It is not a compiler glitch, since g++ 4.1, 4.3 and VS 2005 share the same behavior.

Basically, the virtual operator= behaves differently than any other virtual function with respect to the code that is actually being executed.

struct Base {
   virtual Base& f( Base const & ) {
      std::cout << "Base::f(Base const &)" << std::endl;
      return *this;
   }
   virtual Base& operator=( Base const & ) {
      std::cout << "Base::operator=(Base const &)" << std::endl;
      return *this;
   }
};
struct Derived : public Base {
   virtual Base& f( Base const & ) {
      std::cout << "Derived::f(Base const &)" << std::endl;
      return *this;
   }
   virtual Base& operator=( Base const & ) {
      std::cout << "Derived::operator=( Base const & )" << std::endl;
      return *this;
   }
};
int main() {
   Derived a, b;

   a.f( b ); // [0] outputs: Derived::f(Base const &) (expected result)
   a = b;    // [1] outputs: Base::operator=(Base const &)

   Base & ba = a;
   Base & bb = b;
   ba = bb;  // [2] outputs: Derived::operator=(Base const &)

   Derived & da = a;
   Derived & db = b;
   da = db;  // [3] outputs: Base::operator=(Base const &)

   ba = da;  // [4] outputs: Derived::operator=(Base const &)
   da = ba;  // [5] outputs: Derived::operator=(Base const &)
}

The effect is that the virtual operator= has a different behavior than any other virtual function with the same signature ([0] compared to [1]), by calling the Base version of the operator when called through real Derived objects ([1]) or Derived references ([3]) while it does perform as a regular virtual function when called through Base references ([2]), or when either the lvalue or rvalue are Base references and the other a Derived reference ([4],[5]).

Is there any sensible explanation to this odd behavior?

Was it helpful?

Solution

Here's how it goes:

If I change [1] to

a = *((Base*)&b);

then things work the way you expect. There's an automatically generated assignment operator in Derived that looks like this:

Derived& operator=(Derived const & that) {
    Base::operator=(that);
    // rewrite all Derived members by using their assignment operator, for example
    foo = that.foo;
    bar = that.bar;
    return *this;
}

In your example compilers have enough info to guess that a and b are of type Derived and so they choose to use the automatically generated operator above that calls yours. That's how you got [1]. My pointer casting forces compilers to do it your way, because I tell compiler to "forget" that b is of type Derived and so it uses Base.

Other results can be explained the same way.

OTHER TIPS

There are three operator= in this case:

Base::operator=(Base const&) // virtual
Derived::operator=(Base const&) // virtual
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly

This explains why it looks like Base::operator=(Base const&) is called "virtually" in case [1]. It's called from the compiler-generated version. The same applies to case [3]. In case 2, the right-hand side argument 'bb' has type Base&, so Derived::operator=(Derived&) cannot be called.

There is no user-provided assignment operator defined for Derived class. Hence, compiler synthesizes one and internally base class assignment operator is called from that synthesized assignment operator for Derived class.

virtual Base& operator=( Base const & ) //is not assignment operator for Derived

Hence, a = b; // [1] outputs: Base::operator=(Base const &)

In Derived class, the Base class assignment operator has been overridden and hence, the overridden method gets an entry in virtual table of the Derived class. When the method is invoked via reference or pointers then Derived class overridden method gets called due to VTable entry resolution at run time.

ba = bb;  // [2] outputs: Derived::operator=(Base const &)

==>internally ==> (Object->VTable[Assignement operator]) Get the entry for assignment operator in VTable of the class to which the object belongs and invoke the method.

If you fail to provide an appropriate operator= (i.e. correct return and argument types), the default operator= is provided by the compiler which overloads any user-defined one. In your case it will call the Base::operator= (Base const& ) before copying the Derived members.

Check this link for details on operator= being made virtual.

The reason being there is compiler provided default assignment operator=. Which is called in the scenario a = b and as we know default internally calls base assignment operator.

More explanation about virtual assignment can be found at : https://stackoverflow.com/a/26906275/3235055

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top