By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
435,360 Members | 2,961 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 435,360 IT Pros & Developers. It's quick & easy.

Virtual Assignment Operator in Protocol class

P: n/a
N4M
Dear,
Suppose I have a Protocol class, in which I need also an assignment
operator =().
class B
{
.....
virtual B& operator=(const B& rb) =0;
};
Now in some derived class D: public B, how would I proceed with
operator=? Do I need to supply 2 operators:
-01 to override D& operator=(const B& d)
-and 01 to overload D& operator=(const D& d)?
How about the (im)purality of = ?
Thanks for your guidance.
Jul 22 '05 #1
Share this Question
Share on Google+
17 Replies


P: n/a
N4M wrote:

Dear,
Suppose I have a Protocol class, in which I need also an assignment
operator =().
class B
{
....
virtual B& operator=(const B& rb) =0;
};
Now in some derived class D: public B, how would I proceed with
operator=? Do I need to supply 2 operators:
-01 to override D& operator=(const B& d)
-and 01 to overload D& operator=(const D& d)?
How about the (im)purality of = ?
Thanks for your guidance.


In a nutshell: having op= as virtual is seldome a good idea. Mostly
because it doesn't work how most people would expect it to work.
Hint: polymorphism works only by examining the object the function
is called for, but doesn't take the runtime type of the arguments
into account.

Assume class D, derived from class B

B* p1 = new D;
B* p2 = new D;

*p1 = *p2;

In the above, what do you think the compiler is looking for
when searching a function to fullfil the request of the
assignment? :

1) B::operator=( const B& Arg );
or 2) B::operator=( const D& Arg );
or 3) D::operator=( const B& Arg );
or 4) D::operator=( const D& Arg );

--
Karl Heinz Buchegger
kb******@gascad.at
Jul 22 '05 #2

P: n/a
Karl Heinz Buchegger wrote:

N4M wrote:

Dear,
Suppose I have a Protocol class, in which I need also an assignment
operator =().
class B
{
....
virtual B& operator=(const B& rb) =0;
};
Now in some derived class D: public B, how would I proceed with
operator=? Do I need to supply 2 operators:
-01 to override D& operator=(const B& d)
-and 01 to overload D& operator=(const D& d)?
How about the (im)purality of = ?
Thanks for your guidance.
In a nutshell: having op= as virtual is seldome a good idea. Mostly
because it doesn't work how most people would expect it to work.
Hint: polymorphism works only by examining the object the function
is called for, but doesn't take the runtime type of the arguments
into account.


After rethinking (and smoking a cigarette) I don't think this to be
relevant any more. Read on ...

Assume class D, derived from class B

B* p1 = new D;
B* p2 = new D;

*p1 = *p2;

In the above, what do you think the compiler is looking for
when searching a function to fullfil the request of the
assignment? :

1) B::operator=( const B& Arg );
or 2) B::operator=( const D& Arg );
or 3) D::operator=( const B& Arg );
or 4) D::operator=( const D& Arg );


Next thought experiment (you might want to try this with your compiler
anyway): make the operator virtual.
Which op= is called then?

Hint: Think about what a dereference operation does when the runtime
type of an object differs from the static type of the pointer pointing
to it.

--
Karl Heinz Buchegger
kb******@gascad.at
Jul 22 '05 #3

P: n/a

"Karl Heinz Buchegger" <kb******@gascad.at> wrote in message
news:41***************@gascad.at...

In a nutshell: having op= as virtual is seldome a good idea. Mostly
because it doesn't work how most people would expect it to work.
Hint: polymorphism works only by examining the object the function
is called for, but doesn't take the runtime type of the arguments
into account.
Then programmer must do that, no?
Assume class D, derived from class B

B* p1 = new D;
B* p2 = new D;

*p1 = *p2;

In the above, what do you think the compiler is looking for
when searching a function to fullfil the request of the
assignment? :

1) B::operator=( const B& Arg );
or 2) B::operator=( const D& Arg );
or 3) D::operator=( const B& Arg );
or 4) D::operator=( const D& Arg );
The symptoms you demonstrate do not exist (nor become better
or worse) because of virtual assignment, but because of how
C++ object model works. In real world, virtual assignment is not
the disease, but the cure!

Consider this example:

// ========================================

struct B
{
// this is a pretty canonical class, except for virtual assignment

virtual B &operator=( const B & );

// virtual destructor needed for derived deletion thru base *

virtual ~B();

// add more members (data or functions) to suit the taste...
};
struct D : public B
{
int value;

D( int i=0 ) : value(i)
{
}

D( const D &r ) : B(r) , value(r.value)
{
}

virtual D &operator=( const D &r )
{
B::operator=( r );
value = r.value;
return *this;
}

virtual B &operator=( const B &r )
{
const D *p = dynamic_cast<const D *>(&r);

if( p )
{
*this = *p;
}
else
{
// D is being sliced, but now you can detect and compensate!!!
B::operator=( r );
value = 0;
}

return *this;
}
};

// ========================================

Now your example...
B* p1 = new D;
B* p2 = new D;

*p1 = *p2;
.... will assign as-if using the most intuitive choice...
1) B::operator=( const B& Arg );
or 2) B::operator=( const D& Arg );
or 3) D::operator=( const B& Arg );
or 4) D::operator=( const D& Arg );


.... which, of course, is 4 .

- Risto -
Jul 22 '05 #4

P: n/a
Risto Lankinen wrote:


... which, of course, is 4 .

- Risto -


Thanks for bringing me back on track.
I don't know why, but somehow I always get lost when
thinking about a virtual op= :-)
--
Karl Heinz Buchegger
kb******@gascad.at
Jul 22 '05 #5

P: n/a
N4M
"Risto Lankinen" <rl******@hotmail.com> wrote in message news:<fE*******************@news1.nokia.com>...
"Karl Heinz Buchegger" <kb******@gascad.at> wrote in message
news:41***************@gascad.at...

Thanks for your example, but what if it is insisted to have all
operators and functions in the ABC be PURE ? Then I cannot initiate B
b...
That's driving my crazy.
N4M
Jul 22 '05 #6

P: n/a
N4M
"Risto Lankinen" <rl******@hotmail.com> wrote in message news:<fE*******************@news1.nokia.com>...
"Karl Heinz Buchegger" <kb******@gascad.at> wrote in message
news:41***************@gascad.at...

Thanks for your example, but what if it is insisted to have all
operators and functions in the ABC be PURE ? Then I cannot initiate B
b...
That's driving my crazy.
N4M
Jul 22 '05 #7

P: n/a
N4M wrote:

"Risto Lankinen" <rl******@hotmail.com> wrote in message news:<fE*******************@news1.nokia.com>...
"Karl Heinz Buchegger" <kb******@gascad.at> wrote in message
news:41***************@gascad.at...

Thanks for your example, but what if it is insisted to have all
operators and functions in the ABC be PURE ? Then I cannot initiate B
b...
That's driving my crazy.


I don't understand.

Making those functions PURE (by adding = 0), doesn't mean that
you cannot provide an implementation for them :-)

--
Karl Heinz Buchegger
kb******@gascad.at
Jul 22 '05 #8

P: n/a
dn******@yahoo.com (N4M) wrote:
Dear,
Suppose I have a Protocol class, in which I need also an assignment
operator =().
class B
{
....
virtual B& operator=(const B& rb) =0;
};
Now in some derived class D: public B, how would I proceed with
operator=?
Let's consider this tree:

class B {
public:
virtual ~B() { }
virtual B& operator=( const B& ) = 0;
};

class D {
int foo;
public:
virtual B& operator=( const B& );
};

class C {
double bar;
public:
virtual B& operator=( const B& );
};

Can either D::op= or C::op= be implemented reasonably? I don't think so.
What is supposed to happen with this code?

void fun( B& b1, B& b2 ) {
b1 = b2;
}

If b1 is an D and b2 is a C?

Do I need to supply 2 operators:
-01 to override D& operator=(const B& d)
You need to provide this one.
-and 01 to overload D& operator=(const D& d)?


You would only need this one if something in the class requires it,
otherwise it will be created properly for you.

The moral of the story here is "don't provide a pure virtual op=".
Jul 22 '05 #9

P: n/a

"Daniel T." <po********@eathlink.net> wrote in message news:postmaster->
Let's consider this tree:

class B {
public:
virtual ~B() { }
virtual B& operator=( const B& ) = 0;
};

class D {
int foo;
public:
virtual B& operator=( const B& );
};

class C {
double bar;
public:
virtual B& operator=( const B& );
};

Can either D::op= or C::op= be implemented reasonably?
Absolutely. Here's one way:

B &D::operator=( const B &r )
{
const D *p = dynamic_cast<const D *>(&r);
if( p )
foo = p->foo;
else
throw "Silly assignment!";
return *this;
}

B &C::operator=( const B &r )
{
const C *p = dynamic_cast<const C *>(&r);
if( p )
bar = p->bar;
else
throw "Silly assignment!";
return *this;
}
What is supposed to happen with this code?

void fun( B& b1, B& b2 ) {
b1 = b2;
}

If b1 is an D and b2 is a C?
With virtual assignment, it would throw a "Silly assignment!"
exception (or alternatively, whatever else the author of the
class D deems appropriate for the case when, so to speak,
an Orange is assigned to a Banana thru a reference to Fruit).

WithOUT virtual assignment it would create a banana the size
of a grape, with an orange skin that doesn't need to be peeled
before eating.
The moral of the story here is "don't provide a pure virtual op=".


This belief, I think, needs reconsideration.

- Risto -
Jul 22 '05 #10

P: n/a
In article <Iu*******************@news1.nokia.com>,
"Risto Lankinen" <rl******@hotmail.com> wrote:
"Daniel T." <po********@eathlink.net> wrote in message news:postmaster->
Let's consider this tree:

class B {
public:
virtual ~B() { }
virtual B& operator=( const B& ) = 0;
};

class D {
int foo;
public:
virtual B& operator=( const B& );
};

class C {
double bar;
public:
virtual B& operator=( const B& );
};

Can either D::op= or C::op= be implemented reasonably?


Absolutely. Here's one way:

B &D::operator=( const B &r )
{
const D *p = dynamic_cast<const D *>(&r);
if( p )
foo = p->foo;
else
throw "Silly assignment!";
return *this;
}

B &C::operator=( const B &r )
{
const C *p = dynamic_cast<const C *>(&r);
if( p )
bar = p->bar;
else
throw "Silly assignment!";
return *this;
}


I wouldn't call that a reasonable implementation, but maybe that's just
me. If the "Silly assignment" is thrown, the calling code can't do
anything resonable with it and in order for the calling code to avoid
the throw, it must use RTTI to ensure both arguments are the same
derived type, then call op= which again uses dynamic_cast. That sounds
quite expensive...
Jul 22 '05 #11

P: n/a
N4M
> This belief, I think, needs reconsideration.

- Risto -


How do you think about this solution:

class D; //forward
class B
{
public:
virtual ~B() = 0;
virtual B& operator=(const B&b) = 0;
virtual void Assign(D&) const = 0;
};
B::~B() {}
class D: public B
{
public:
explicit D(int i):m_n(i) {}
~D() {}
B& operator=(const B& rb) {
rb.Assign(*this);
return dynamic_cast<B&>(*this);//static_cast is fine, too.
}
virtual void Assign(D& rd) const { rd.m_n = m_n;}
int GetValue() {return m_n;}
private:
int m_n;

};
// Test
void Test(B& b1, B& b2)
{
b1 = b2;
}
//
int main()
{
D d1(1), d2(2);
cout<<"Before d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
Test(d1,d2);
cout<<"After d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
}
I have tested this, it's fine, and it serves the purpose that all
Derived classes need to define the assignment operator indirectly thru
Assign().
Nguyen Mai.
Jul 22 '05 #12

P: n/a

"Daniel T." <po********@eathlink.net> wrote in message
news:po******************************@news04.east. earthlink.net...

If the "Silly assignment" is thrown, the calling code can't do
anything resonable with it...
First, I'll repaste a section deleted from my original article:
With virtual assignment, it would throw a "Silly assignment!"
exception (or alternatively, whatever else the author of the
class D deems appropriate ...


If you deem the default behaviour (by which I mean simulating
the non-virtual assignment) more appropriate, you could do the
following:

B &D::operator=( const B &r )
{
const D *p = dynamic_cast<const D *>(&r);'
if( p )
// Calls D::operator=( const D & ); Explicit call deliberately
// not used to enable late binding for D::op=(D) in case it has
// been declared virtual in this class and similarly overloaded
// in the next level derived class:
return *this = *p;
else
return B::operator=( p );
}
... and in order for the calling code to avoid the throw it must
use RTTI to ensure both arguments are the same derived type,
then call op= which again uses dynamic_cast. That sounds
quite expensive...


If RTTI is too costly, simply leave it away! But then you can't
completely emulate Derived-to-Derived assignment thru Base
pointers or references. This will still be useful for detecting the
slicing (e.g. to retain the class-level invariants of D consistent
even if someone assigns only a Base part to it):

B &D::operator=( const B &r )
{
B &result = B::operator=( r );
// This 'D' was just sliced by a 'B' called 'r'! Counteract!
return result;
}

Without virtual assignment in base class, this would be impossible.

Cheers!

- Risto -
Jul 22 '05 #13

P: n/a
In article <6e**************************@posting.google.com >,
dn******@yahoo.com (N4M) wrote:
This belief, I think, needs reconsideration.

- Risto -


How do you think about this solution:

class D; //forward
class B
{
public:
virtual ~B() = 0;
virtual B& operator=(const B&b) = 0;
virtual void Assign(D&) const = 0;
};
B::~B() {}
class D: public B
{
public:
explicit D(int i):m_n(i) {}
~D() {}
B& operator=(const B& rb) {
rb.Assign(*this);
return dynamic_cast<B&>(*this);//static_cast is fine, too.
}
virtual void Assign(D& rd) const { rd.m_n = m_n;}
int GetValue() {return m_n;}
private:
int m_n;

};
// Test
void Test(B& b1, B& b2)
{
b1 = b2;
}
//
int main()
{
D d1(1), d2(2);
cout<<"Before d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
Test(d1,d2);
cout<<"After d1, d2:"<< d1.GetValue() <<","<<d2.GetValue()<<endl;
}
I have tested this, it's fine, and it serves the purpose that all
Derived classes need to define the assignment operator indirectly thru
Assign().


You end up needing to add an assign to the base class and implement a
new assign method in every other derived class whenever you to create a
derived class. Very ugly...
Jul 22 '05 #14

P: n/a
dn******@yahoo.com (N4M) wrote:
Dear,
Suppose I have a Protocol class, in which I need also an assignment
operator =().
class B
{
....
virtual B& operator=(const B& rb) =0;
};
Now in some derived class D: public B, how would I proceed with
operator=? Do I need to supply 2 operators:
-01 to override D& operator=(const B& d)
-and 01 to overload D& operator=(const D& d)?
How about the (im)purality of = ?
Thanks for your guidance.


I've been thinking more about this whole situation... Given the above
class (ie op= is pure virtual and the class has no data members) what is
the post-condition of the op=? I mean, usually the post-condition of op=
is that *this == rb after the function, but would:

bool fun( B& r, B& l ) {
return r == l;
}

even compile? If yes, under what conditions should the above return
false? Under what condtions should it return true?
Jul 22 '05 #15

P: n/a
"Risto Lankinen" <rl******@hotmail.com> wrote:
Consider this example: [formatting changed some to save space. I also added a pure virtual
to the base to make it an ABC like the origional example.]
// ========================================

struct B
{ virtual void foo() = 0; virtual B &operator=( const B & );
virtual ~B();
};
struct D : public B
{
int value;
D( int i=0 ) : value(i) { }

D( const D &r ) : B(r) , value(r.value) { } void foo() { }
virtual D &operator=( const D &r ) {
B::operator=( r );
value = r.value;
return *this;
}

virtual B &operator=( const B &r ) {
const D *p = dynamic_cast<const D *>(&r);
if( p ) {
*this = *p;
}
else {
// D is being sliced, but now you can detect and compensate!!!
B::operator=( r );
value = 0;
}
return *this;
}
};


Using the example above, given this code:

struct E: public B {
float value;
B(): value( 0.01 ) { }
void foo() { }
B& operator=( const B& r ) {
const E* p = dynamic_cast< const E* >( &r );
if ( p )
*this = *p;
else {
B::operator=( r );
value = 0.01;
}
return *this;
}
};

void ick( B& l, B& r ) {
l = r;
}

int main() {
E e;
D d;

ick( d, e );
}

Are 'd' and 'e' supposed to be equal after the call to 'ick'? Could 'd'
and 'e' ever be equal? If not, then what is B's op= supposed to do?
Jul 22 '05 #16

P: n/a

"Daniel T." <po********@eathlink.net> wrote in message
news:po******************************@news03.east. earthlink.net...
"Risto Lankinen" <rl******@hotmail.com> wrote:
Consider this example: [formatting changed some to save space. I also added a pure virtual
to the base to make it an ABC like the origional example.]

// ========================================

struct B
{

virtual void foo() = 0;
virtual B &operator=( const B & );
virtual ~B();
};
struct D : public B
{
int value;
D( int i=0 ) : value(i) { }

D( const D &r ) : B(r) , value(r.value) { }

void foo() { }

virtual D &operator=( const D &r ) {
B::operator=( r );
value = r.value;
return *this;
}

virtual B &operator=( const B &r ) {
const D *p = dynamic_cast<const D *>(&r);
if( p ) {
*this = *p;
}
else {
// D is being sliced, but now you can detect and compensate!!!
B::operator=( r );
value = 0;
}
return *this;
}
};


Using the example above, given this code:

struct E: public B {
float value;
B(): value( 0.01 ) { }
void foo() { }
B& operator=( const B& r ) {
const E* p = dynamic_cast< const E* >( &r );
if ( p )
*this = *p;
else {
B::operator=( r );
value = 0.01;
}
return *this;
}
};

void ick( B& l, B& r ) {
l = r;
}

int main() {
E e;
D d;

ick( d, e );
}

Are 'd' and 'e' supposed to be equal after the call to 'ick'?


Well, yes, if you use the same definition of "equal" as
when B &B::op=(B) is non-virtual.
Could 'd' and 'e' ever be equal?


You tell me. Sure you knew that if you implemented
bool operator(B,B) you'd be able compare a C to a D
anyway (again, regardless of whether the assignments
occurred thru virtual or non-virtual operators).

Nevertheless, I don't see it as a big problem that op=()
and op==() lose a bit of consistency in the presence of
type conversions. Even C with no operator overloading
or virtual methods manifests a similar inconsistency:

int i = 12;
double d = 3.4;
i = d;
assert( i == d );
Cheers!

- Risto -
Jul 22 '05 #17

P: n/a
In article <pD*******************@news1.nokia.com>,
"Risto Lankinen" <rl******@hotmail.com> wrote:
"Daniel T." <po********@eathlink.net> wrote in message
news:po******************************@news03.east. earthlink.net...
"Risto Lankinen" <rl******@hotmail.com> wrote:

void ick( B& l, B& r ) {
l = r;
}

int main() {
E e;
D d;

ick( d, e );
}

Are 'd' and 'e' supposed to be equal after the call to 'ick'?
Well, yes, if you use the same definition of "equal" as
when B &B::op=(B) is non-virtual.


That's a little hard to do when B::op=(B) is pure-virtual...
Could 'd' and 'e' ever be equal?


You tell me. Sure you knew that if you implemented
bool operator==(B,B) you'd be able compare a C to a D
anyway (again, regardless of whether the assignments
occurred thru virtual or non-virtual operators).


Let's do that:

struct Base {
int x;
Base( int a ): x(a) { }
virtual ~Base() { }
};

bool operator==( const Base& l, const Base& r ) {
return l.x == r.x;
}

//================================================== ==================
struct Derived : Base {
int y;
Derived( int a, int b ): Base( a ), y( b ) { }
};

//================================================== ==================
void comp( Base& l, Base& r ) {
assert( l == r );
}

int main() {
Derived d1( 0, 1 ), d2( 0, 2 );
comp( d1, d2 );
cout << "OK";
}

(The above prints "OK")
Is this really something anyone would want?
Nevertheless, I don't see it as a big problem that op=()
and op==() lose a bit of consistency in the presence of
type conversions. Even C with no operator overloading
or virtual methods manifests a similar inconsistency:

int i = 12;
double d = 3.4;
i = d;
assert( i == d );


And that causes no end of trouble for those who think that double should
be substitutable for int. It isn't.

By using inheritance (rather than say a conversion function) we are
saying that any type Derived from Base is subsitutable for it.
Jul 22 '05 #18

This discussion thread is closed

Replies have been disabled for this discussion.