Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

virtual operators

0 views
Skip to first unread message

Damien Kick {DKICK1}

unread,
Dec 5, 2000, 3:00:00 AM12/5/00
to
I'm currently looking at some code like the following:

class Foo {
// ...
public:
virtual operator ==(const Foo& rhs) = 0;
};

class Bar : public Foo {
// ...
public:
virtual operator ==(const Bar& rhs);
};

And it looks weird to me. <pause> Thinking about it, I have never
seen an example of a virtual operator. I've a feeling that there is a
good reason for this... I just can't think of one off the top of my
head. Consider the following example included below this paragraph.
Having compiled and tested it with both gcc-2.95.2 and Sun Workshop
5.0, I get the same results: compiling without '-DVIRT' causes the
non-virtual 'operator++()' to slice whilst the virtual 'operator++()'
version finds the "right" method, just as is the case with the virtual
'f'. Isn't the behavior of the virtual operator what one would want
to happen? Why are all of the examples of operator overloading that I
can find in my C++ reference books (Stroustrup, Meyers) non-virtual?
Is there a reason for this or is this lack purely coincidental?

#include <iostream>

class B {
int _i;
public:
explicit B(int i) : _i(i)
{ }
virtual void f() const
{ std::cout << "B::f()\n"; }
#ifdef VIRT
virtual
#endif
const B& operator ++()
{
std::cout << "B::operator ++(): _i = " << _i << "\n";
return *this;
}
};

class D : public B {
int _j;
public:
explicit D(int i, int j) : B(i), _j(j)
{ }
virtual void f() const
{ std::cout << "D::f()\n"; }
#ifdef VIRT
virtual
#endif
const D& operator ++()
{
std::cout << "D::operator ++(): _j = " << _j << "\n";
return *this;
}
};

int
main()
{
D d(6, 9);
B& b_ref_to_d = d;
b_ref_to_d.f();
++b_ref_to_d;

D* d_ptr = new D(7, 2);
B* b_ptr_to_d_ptr = d_ptr;
(*b_ptr_to_d_ptr).f();
++(*b_ptr_to_d_ptr);
}

--
Damien Kick


[ Send an empty e-mail to c++-...@netlab.cs.rpi.edu for info ]
[ about comp.lang.c++.moderated. First time posters: do this! ]

Kevin Cline

unread,
Dec 6, 2000, 3:00:00 AM12/6/00
to
Damien Kick {DKICK1} wrote:

> I'm currently looking at some code like the following:
>
> class Foo {
> // ...
> public:
> virtual operator ==(const Foo& rhs) = 0;
> };
>
> class Bar : public Foo {
> // ...
> public:
> virtual operator ==(const Bar& rhs);
> };
>
> And it looks weird to me. <pause> Thinking about it, I have never
> seen an example of a virtual operator.

Virtual function call operators are very common, and operator== is often
virtual approximately as shown in your example. However, I see several
errors in the posted code. Most importantly, Bar::operator== doesn't
override Foo::operator== because it has a different argument list. Also,
operator== is usually a member function returning bool.

Instead the code should read:

class Bar: public Foo {
public:
bool operator==(const Bar& bar) const
{ return ... }
virtual bool operator==(const Foo& rhs) const
{ const Bar* bp = dynamic_cast<const Bar*>(&rhs);
return bp && *this == *bp;
}
};

James Kanze

unread,
Dec 6, 2000, 3:00:00 AM12/6/00
to
Damien Kick {DKICK1} wrote:

> And it looks weird to me. <pause> Thinking about it, I have never

> seen an example of a virtual operator. I've a feeling that there is a
> good reason for this...

Many operators don't work well when virtual. A virtual operator=
would be a good example of this. In fact, to make most binary
operators work well in the traditional uses, you would need some form
of double dispatch. Unary operators, however, don't necessarily cause
problems.

And there are exceptions with regards to binary operators. Consider
the classical solution of matrix arithmetic, where the operators don't
return a new matrix (very expensive), but simply nodes in a parse
tree, and assignment and copy are overloaded to accept a reference to
a parse tree and create the new matrix in place. In this case, the
operators work well with inheritance, because they don't actually use
the operands directly anyway.

(As an example of what I'm talking about:

class MatrixOp
{
public:
virtual ~MatrixOp() {}
virtual int getX() const = 0 ;
virtual int getY() const = 0 ;
virtual double getAt( int x , int y ) const = 0 ;
// ...
} ;

class MatrixFacade : public MatrixOp
{
public:
MatrixFacade( Matrix const& owner )
: myOwner( owner )
{
}

virtual int getX() const
{
return myOwner.getX() ;
}

virtual int getY() const
{
return myOwner.getY() ;
}

virtual double getAt( int x , int y ) const
{
return myOwner.getAt( x , y ) ;
}
private:
Matrix const& myOwner ;
} ;

class MatrixAdd : public MatrixOp
{
public:
MatrixAdd( MatrixOp const& left ,
MatrixOp const& right )
: myLeft( left )
, myRight( right )
{
assert( myLeft.getX() == myRight.getX()
&& myLeft.getY() == myRight.getY() ) ;
}

virtual int getX() const
{
return myLeft.getX() ;
}

virtual int getY() const
{
return myLeft.getY() ;
}

virtual double getAt( int x , int y ) const
{
return myLeft.getAt( x , y ) + myRight.getAt( x , y ) ;
}

private:
MatrixOp const& myLeft ;
MatrixOp const& myRight ;
} ;

And so on.

--
James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

Risto Lankinen

unread,
Dec 6, 2000, 3:00:00 AM12/6/00
to
Hi!

James Kanze <James...@dresdner-bank.com> wrote in message
news:3A2E0CA1...@dresdner-bank.com...


>
> Many operators don't work well when virtual. A virtual operator=
> would be a good example of this.

I beg to differ.

Virtual operator=() works _absolutely_brilliantly_ to prevent what is
called object slicing. By overriding the base class' operator=() the
derived class can detect (using RTTI) if the value being assigned is
of the same type and cast appropriately. Example follows:

class Base
{
public:
virtual Base &operator=( const Base & ) { ... }
};

class Derived : public Base
{
public:
virtual Base &operator=( const Base &r )
{
Derived *p = dynamic_cast<Derived *>(&r);
if( p )
{
*this = *p;
}
else
{
/* do semantically appropriate upcast
of 'r' and assign from that */
}
return *this;
}

virtual Derived &operator=( const Derived & ) { ... }
};


Cheers!

- Risto -

James Kanze

unread,
Dec 7, 2000, 10:28:02 AM12/7/00
to
Risto Lankinen wrote:

> James Kanze <James...@dresdner-bank.com> wrote in message
> news:3A2E0CA1...@dresdner-bank.com...

> > Many operators don't work well when virtual. A virtual operator=
> > would be a good example of this.

> I beg to differ.

> Virtual operator=() works _absolutely_brilliantly_ to prevent what
> is called object slicing. By overriding the base class' operator=()
> the derived class can detect (using RTTI) if the value being
> assigned is of the same type and cast appropriately.

And what can it do if the value being assigned isn't the same type?
Remember, the signature is operator=( Base const& ).

> Example follows:

> class Base
> {
> public:
> virtual Base &operator=( const Base & ) { ... }
> };

> class Derived : public Base
> {
> public:
> virtual Base &operator=( const Base &r )
> {
> Derived *p = dynamic_cast<Derived *>(&r);
> if( p )
> {
> *this = *p;
> }
> else
> {
> /* do semantically appropriate upcast
> of 'r' and assign from that */

This is precisely why it doesn't work well. What is a "semantically
appropriate upcast"?

When correctly overloaded, I would expect that after the operation
a=b, a==b is true. If a and b are different types, how do you do
this. And if you don't do it, is assignment really the correct
operator; I would find the results confusing, at the least.

> }
> return *this;
> }

> virtual Derived &operator=( const Derived & ) { ... }
> };

--


James Kanze mailto:ka...@gabi-soft.de
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelhüttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627

Risto Lankinen

unread,
Dec 7, 2000, 1:04:51 PM12/7/00
to
Hi!

James Kanze <James...@dresdner-bank.com> wrote in message

news:3A2F64CE...@dresdner-bank.com...


> Risto Lankinen wrote:
>
> > Virtual operator=() works _absolutely_brilliantly_ to prevent what
> > is called object slicing. By overriding the base class' operator=()
> > the derived class can detect (using RTTI) if the value being
> > assigned is of the same type and cast appropriately.
>
> And what can it do if the value being assigned isn't the same type?
> Remember, the signature is operator=( Base const& ).

Like I said, you upcast as appropriate. Read on...

> > Base &Derived::operator=( const Base &r ) // virtual


> > {
> > Derived *p = dynamic_cast<Derived *>(&r);
> > if( p )
> > {
> > *this = *p;
> > }
> > else
> > {
> > /* do semantically appropriate upcast
> > of 'r' and assign from that */
>
> This is precisely why it doesn't work well. What is a "semantically
> appropriate upcast"?

It's an upcast that preserves the meaning of the original object's state.

Typically all my derived classes have a constructor that takes an
instance of its base class, plus any data that the derived class adds.
For a simplified example, take the following declaration:

class WindowPosition : public Position
{
HWND hWnd;
WindowPosition( const Position &,HWND );
}

Then, a semantically appropriate upcast would be:

Position &WindowPosition::operator=( const Position &r )
{
. . .
else return *this = WindowPosition( r,hWnd );
}

If you find it hard to figure out what's semantically appropriate,
you can always simply just...

return Base::operator=( r );

... and be no worse off than without the virtual assignment
anyway.

> When correctly overloaded, I would expect that after the operation
> a=b, a==b is true.

I would expect that, too. Fortunately, it's easy to make so
(if nothing else helps, make the operator==() virtual, too!)

BTW, the topic of my original article was to show that virtual
operator=() is useful. While object slicing prevention is one
reason, there are a couple of other uses, too, that I can think
of.

Bottom line: In my experience making operator=() virtual is
a very useful feature.

Cheers!

- Risto -

Ken Hagan

unread,
Dec 8, 2000, 8:25:46 AM12/8/00
to
Listo Rinkanen wrote...

>
> Like I said, you upcast as appropriate. Read on...
>
> [snip, solution involving knowledge of the derived class]

How do you handle the following?

class Derived1 : public Base { ... };
class Derived2 : public Base { ... };

Derived1 d1;
Derived2 d2;
d2 = d1;

You may assume that Derived1 and Derived2 were written
by different people and neither declaration is visible to
the other. (The assignment is happening in a lower level
module which is used by both groups.)

If you have a limited number of derived classes that all know
about each other, then you will get away with this design.
If the system is open for extension (derivation in this case),
then as soon as two separate extensions are made you can no
longer make it work.

However, if you know all the possible derived classes, then
your operator= needn't be virtual. You can up-cast both
arguments. This has the advantage that all your possibilities
are in the one place, and if you miss a permutation then it
is much more obvious. If you write the function as a large
if/else tree, then you can assert at the bottom that you found
a good match. Your maintenance programmers will thank you
for this.

Either way, a virtual operator= is not needed, and merely
creates the possibility of future error.

Risto Lankinen

unread,
Dec 9, 2000, 12:25:17 AM12/9/00
to
Hi!

Hen Kanag <K.H...@thermoteknix.co.uk> wrote


> Listo Rinkanen wrote...
>
> > Like I said, you upcast as appropriate. Read on...
> >
> > [snip, solution involving knowledge of the derived class]

Let me paste back what you had snipped:

> > Base &Derived::operator=( const Base &r ) // virtual
> > {
> > Derived *p = dynamic_cast<Derived *>(&r);
> > if( p )
> > {
> > *this = *p;
> > }
> > else
> > {
> > /* do semantically appropriate upcast
> > of 'r' and assign from that */

I guess you overlooked the "Derived::" in my sample...

The notation "Derived::operator=()" makes this a member
function of the Derived class. Why shouldn't a method of
a class involve knowledge of itself? It takes and returns a
Base object in order to have the correct signature so that
it overrides the virtual "Base::operator=()", whereas Base
itself doesn't need to know a Derived exists.

> How do you handle the following?
>
> class Derived1 : public Base { ... };
> class Derived2 : public Base { ... };
>
> Derived1 d1;
> Derived2 d2;
> d2 = d1;
>
> You may assume that Derived1 and Derived2 were written
> by different people and neither declaration is visible to
> the other. (The assignment is happening in a lower level
> module which is used by both groups.)

In "d2 = d1" the "Derived2::operator=(const Base&)" will be
invoked with the "d1" as a "Base"-type argument. Voila!

> If the system is open for extension (derivation in this case),
> then as soon as two separate extensions are made you can no
> longer make it work.

Humbug! Example below should explain:

>>>>> carved_in_stone_third_party_stuff.h <<<<<

struct Position
{
...
/* The point of debate - virtual or not: */
virtual Position &operator=( const Position & );

virtual int GetX() const;
virtual int GetY() const;
...
};

struct Displacement;

Position operator+( const Position &,const Displacement & );
Displacement operator-( const Position &,const Position & );
Displacement operator/( const Displacement &,int );

void PutInTheMiddle( Position &,const Position &,const Position & );

/* Implementation is in "carved_in_stone_third_party_stuff.lib".
Source code for implementation is not available. However,
for the sake of illustration, the function PutInTheMiddle()
might look like this:

void PutInTheMiddle( Position &x,const Position &a,const Position &b )
{
x = a + (b-a)/2;
}

*/

>>>>> first_separate_derivation.h <<<<<

#include "carved_in_stone_third_party_stuff.h"

class WindowPos : public Position
{
...
HWND hWnd;
public:
WindowPos( const Position &,HWND );
virtual Position &operator=( const Position &r )
{
return *this = WindowPos( r,this->hWnd );
}

virtual WindowPos &operator=( const WindowPos &r )
{
POINT pt;
this->hWnd = r.hWnd;
pt.x = r.GetX();
pt.y = r.GetY();
SetWindowPosition( hWnd,&pt );
return *this;
}

int GetX()
{
POINT pt;
GetWindowPosition( hWnd,&pt );
return pt.x;
}

int GetY() { /* likewise */ }
...
};

class Window
{
...
public:
WindowPos GetTopLeft() const { ... }
WindowPos GetBottomRight() const { ... }
};

>>>>> second_separate_derivant.h <<<<<

#include "carved_in_stone_third_party_stuff.h"

class CursorPos : public Position
{
...
public:
CursorPos( const Position & );
virtual Position &operator=( const Position &r )
{
*this = CursorPos( r );
}
virtual CursorPos &operator=( const CursorPos &r )
{
MoveCursor( r.GetX(),r.GetY() );
}
...
};


>>>>> your_app.h <<<<<

#include "first_separate_derivant.h"
#include "second_separate_derivant.h"

Window window;
CursorPos cursor;

...
/* Center the cursor in the window */
PutInTheMiddle( cursor,window.TopLeft(),window.BottomRight() );
...

>>>>> END <<<<<

> However, if you know all the possible derived classes, then
> your operator= needn't be virtual.

There are many other uses for virtual assignment operator, too.

> Either way, a virtual operator= is not needed, and merely
> creates the possibility of future error.

Your mileage clearly varies, but I've been a happy camper with
this technique.

Cheers!

- Risto -

P.S. Please spell my name correctly in the future and I might
do the same with yours.

Ken Hagan

unread,
Dec 12, 2000, 3:33:14 PM12/12/00
to

"Risto Lankinen" <rlan...@hotmail.com> wrote...

>
> Let me paste back what you had snipped:
>
> > > Base &Derived::operator=( const Base &r ) // virtual
> > > {
> > > Derived *p = dynamic_cast<Derived *>(&r);
> > > if( p )
> > > {
> > > *this = *p;
> > > }
> > > else
> > > {
> > > /* do semantically appropriate upcast
> > > of 'r' and assign from that */
>
> I guess you overlooked the "Derived::" in my sample...

No, but with two independent derivations you will in general
have no upcast to perform. We seem to agree on this, since you
reply...

> In "d2 = d1" the "Derived2::operator=(const Base&)" will be
> invoked with the "d1" as a "Base"-type argument. Voila!

...and your example doesn't use a dynamic_cast.

> Your mileage clearly varies, but I've been a happy camper with
> this technique.

My point was that if your design requires an operator= that
understands the whole class hierachy, then it isn't going to
scale well, and if it doesn't need to understand it, then you
can live without a virtual operator=. You seem to be arguing
that there is a middle ground where a class needs to understand
its own patch of the hierarchy, but no more. I've never met that
situation, but I won't argue with a happy camper.

> Cheers!
>
> - Risto -
>
> P.S. Please spell my name correctly in the future and I might
> do the same with yours.

Sorry.

0 new messages