virtual assignment operators

Started by
17 comments, last by ChaosEngine 20 years, 1 month ago
ok C++ oo guru types... virtual assignment operators: good or bad? Personnally I would never have thought to make an assignment operator virtual...but in my new job there are several such operators. Problem is when the a class is dervied from the base, the compiler complains that the virtual = op is hidden. The derived class obviously needs it''s own assignment op in order to correctly copy the derived data, and can just call the base impl. Am I missing something? Any thoughts? "That''s not a bug, it''s a feature!" --me
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight
Advertisement
My personal opinion is virtual assignment is evil, especially virtual copy assignment. In almost all cases a design that uses virtual assignment can be reimplemented in terms of non-operator member functions. (edit: That is, non-operator virtual functions.)

One of the evils of virtual copy assignment is that it can allow a two classes derived to the same base to be assigned to each other, which rarely makes sense.

Another issue with virtual copy assignment is that proper implementation of a virtual copy assignment operator requires an override for each and every assignment operator defined by the base classes of a derived class, which often leads to an geometric growth of assignment operator implementations.

One final evilness of virtual copy assignment is that it promotes unnecessary coupling between classes.

And this is just off the top of my head. I could probably rant on for a while longer if I did some research.

[edited by - SiCrane on March 18, 2004 9:05:48 PM]
My first reaction was that a virtual assignment operator is not useful. Then I did some quick tests and found that a virtual assignment operator is useful for overcoming a problem with slicing. I don''t know if that is the best way to solve the problem. The slicing problem can be handled because the derived class has a chance to synchronize its state with the base class. I don''t think it is evil because it doesn''t create any extra potential for problems or abuse, even though it is unusual.
quote:Original post by SiCrane
...it can allow a two classes derived to the same base to be assigned to each other...
Not true. The assignment is possible with or without overloading the assignment operator, virtual or non-virtual.
quote:Original post by SiCrane
...proper implementation of a virtual copy assignment operator requires an override for each and every assignment operator...
Only if the base class function is pure. Otherwise, it is just like any other virtual function.

John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
quote:Original post by JohnBolton
My first reaction was that a virtual assignment operator is not useful. Then I did some quick tests and found that a virtual assignment operator is useful for overcoming a problem with slicing.

In general, virtual copy assignment doesn't solve slicing. Assigning a derived class to a base class will not magically give the base class the derived class's member variables or update the base class's vtable.

quote:
quote:Original post by SiCrane
...it can allow a two classes derived to the same base to be assigned to each other...

Not true. The assignment is possible with or without overloading the assignment operator, virtual or non-virtual.

Be consistent. First you say that it's not possible then you say that it is possible. I'll assume that you mean that it is possible, because that's the longer of the two sentences you typed. In which case you would be wrong. Assignment between derived classes cannot be performed unless the derived classes specifically define assignment operators for the base class or the other derived class. This is due to function hiding and automatic assigment operator generation. For example, this code will not compile on a standards compliant compiler:
class Base {  public:    Base & operator=(const Base &) { return *this; }};class Derived1 : public Base {};class Derived2 : public Base {};int main(int, char **) {  Derived1 d1;  Derived2 d2;  d1 = d2;  return 0;} 
quote:
quote:Original post by SiCrane
...proper implementation of a virtual copy assignment operator requires an override for each and every assignment operator...
Only if the base class function is pure. Otherwise, it is just like any other virtual function.

Wrong again. Assignment operators are one of the small number of functions that that compiler generates implicitly for each class. And the assignment operator generated by the compiler is a non-virtual function that will hide the virtual function in the base class. This makes it unlike other virtual functions.

edit: formatting

[edited by - SiCrane on March 19, 2004 2:28:18 AM]
quote:Original post by SiCrane
quote:Original post by JohnBolton
...found that a virtual assignment operator is useful for overcoming a problem with slicing.

In general, virtual copy assignment doesn''t solve slicing...


The code I tried was something like this:
class A  { .... };class B : public A { .... };class C : public A { .... };...A   a;A * pb = new B;A * pc = new C;*pb = *pc;*pc = a;  
Overriding the A''s virtual assignment operator in B with
B const & B::operator=( A const & a ) {...}  
allows B to deal with any slicing issues that may occur. I tried it. It worked.
quote:Original post by SiCrane
quote:Original post by JohnBolton
quote:Original post by SiCrane
...it can allow a two classes derived to the same base to be assigned to each other...
Not true...
...Assignment between derived classes cannot be performed unless the derived classes specifically define assignment operators for the base class or the other derived class...
Well, the code above compiles and runs without implementing any assignment operators. It also compiles and runs if only an assignment operator for A is provided, virtual or non-virtual. That is what I meant.
quote:Original post by SiCrane
quote:Original post by JohnBolton
quote:Original post by SiCrane
...proper implementation of a virtual copy assignment operator requires an override for each and every assignment operator...
Only if the base class function is pure. Otherwise, it is just like any other virtual function.
Wrong again.
Well, I based my statement on the things I tried. If nothing was overridden, the assignment copied the member variables of A. If I overrode A''s assignment operator in A (virtual or not) but not B, the overriding function in A was called. If I overrode A''s assignment operator in A (virtual) and in B, then B''s function was called. I don''t see any difference between this and how other virtual functions work.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
quote:Original post by JohnBolton
I tried it. It worked.

It may have invoked the proper virtual function, but did it reassign virtual function table pointers or magically grow member variables defined in the C class in the B object? I don''t think you understand the definition of slicing.

quote:
Well, the code above compiles and runs without implementing any assignment operators. It also compiles and runs if only an assignment operator for A is provided, virtual or non-virtual. That is what I meant.

And it will fail when working with the concrete types rather than the abstract types. When additional type information causes an operation to fail when limited type information allows the operation to succeed, that''s what we call a warning sign. C++ is a strongly typed language for a reason. Attempting to subvert the type system is deliberately asking for trouble.

quote:Well, I based my statement on the things I tried. If nothing was overridden, the assignment copied the member variables of A. If I overrode A''s assignment operator in A (virtual or not) but not B, the overriding function in A was called. If I overrode A''s assignment operator in A (virtual) and in B, then B''s function was called. I don''t see any difference between this and how other virtual functions work.

The difference was you were working with only the abstract types. The behavior of the concrete types is another story. And if there''s a disjunction between the behaviour of an object when accessed via an abstract type versus its behaviour when accessed with its concrete type, then we have bad mojo (or "evilness").

In contrast, if we defined a normal virtual function, say twiddle() in the base class, then this schizophrenic behaviour does not occur with respect to twiddle() in the derived classes. (Unless you go out of your way to induce schizophrenia.) This is why a virtual assignment operator is unlike a normal virtual function.
quote:Original post by JohnBolton
The code I tried was something like this:
class A  { .... };class B : public A { .... };class C : public A { .... };...A   a;A * pb = new B;A * pc = new C;*pb = *pc;*pc = a;     
Overriding the A''s virtual assignment operator in B with
B const & B::operator=( A const & a ) {...}     
allows B to deal with any slicing issues that may occur. I tried it. It worked.


I dont see what you have gained here, you still only have access to the A member variables of the a parameter in B::operator=. To avoid ''slicing'', as you put it, you would need to implement some form of double dispatch here.
quote:Original post by Jingo
To avoid ''slicing'', as you put it, you would need to implement some form of double dispatch here.

Double dispatch won''t help with slicing. The fundamental problem is that the concrete type of a given lvalue is immutable under standard C++ rules. Neither double dispatch or virtual assignment will change that.
Why wont double dispatch help?

A* pb = new b;A* pb2 = new b;assign(pb, pb2); //calls b::operator=(b&) using double dispatch


Problem solved, the correct operator = would have access to all members of the dynamic type.
The problem is when you assign a B object to an A object or a C object. Neither assignment generally makes sense, and both assignments are syntactically possible with a double dispatch approach. In both cases there are most likely data members that exist in the assigned type don''t exist in the assignee type. This leads to classic slicing.

This topic is closed to new replies.

Advertisement