is operator= not polimorphic (virtual)?

H

Heiner

#include <stdio.h>

class A
{
public:
virtual A & operator= (const A &);
virtual void test(const A &);
};

class B : public A
{
public:
virtual A & operator= (const A &);
virtual void test(const A &);
};

A & A::eek:perator= (const A & src)
{
printf("A= called\n");
return * this;
}

void A::test(const A & src)
{
printf("A::test called\n");
}

A & B::eek:perator= (const A & src)
{
printf("B= called\n");
return * this;
}

void B::test(const A & src)
{
printf("B::test called\n");
}

int main (int)
{
A a;
B b1, b2;
printf("b1 = a: "); b1 = a;
printf("b1 = b2: "); b1 = b2;
printf("b1.test(a): "); b1.test(a);
printf("b1.test(b2): "); b1.test(b2);
return 0;
}

I would have guessed, that b1 = b2 calls B::eek:perator=, as b1.test(b2)
calls B::test. But what I get is:

7of9# gmake && ./test
g++ main.cpp -o test
b1 = a: B= called
b1 = b2: A= called
b1.test(a): B::test called
b1.test(b2): B::test called
7of9# g++ --version
g++ (GCC) 3.4.2 [FreeBSD] 20040728
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Is operator= not polymorphic?


Heiner
(e-mail address removed)
Remove the nospam to get my real address
 
M

Markus Moll

Heiner said:
#include <stdio.h>

class A
{
public:
virtual A & operator= (const A &);
virtual void test(const A &);
};

class B : public A
{
public:
virtual A & operator= (const A &);
virtual void test(const A &);
}; [...]
I would have guessed, that b1 = b2 calls B::eek:perator=, as b1.test(b2)
calls B::test. But what I get is: [...]
b1 = a: B= called
b1 = b2: A= called
b1.test(a): B::test called
b1.test(b2): B::test called [...]
Is operator= not polymorphic?

Yes, but...
There is more than one operator= involved, and that is your problem.
You do not provide a default assignment operator for class B, so the
compiler generates one with signature operator=(const B&);
This assignment operator works by assigning base classes and members
individually, thus invoking A::eek:perator=(const A&). This produces the
output you observe.

What exactly do you want to do?

Markus
 
H

Heinz Ozwirk

Heiner said:
#include <stdio.h>

class A
{
public:
virtual A & operator= (const A &);
virtual void test(const A &);
};

class B : public A
{
public:
virtual A & operator= (const A &);
virtual void test(const A &);
};

A & A::eek:perator= (const A & src)
{
printf("A= called\n");
return * this;
}

void A::test(const A & src)
{
printf("A::test called\n");
}

A & B::eek:perator= (const A & src)
{
printf("B= called\n");
return * this;
}

void B::test(const A & src)
{
printf("B::test called\n");
}

int main (int)
{
A a;
B b1, b2;
printf("b1 = a: "); b1 = a;
printf("b1 = b2: "); b1 = b2;
printf("b1.test(a): "); b1.test(a);
printf("b1.test(b2): "); b1.test(b2);
return 0;
}

I would have guessed, that b1 = b2 calls B::eek:perator=, as b1.test(b2)
calls B::test. But what I get is:

7of9# gmake && ./test
g++ main.cpp -o test
b1 = a: B= called
b1 = b2: A= called
b1.test(a): B::test called
b1.test(b2): B::test called
7of9# g++ --version
g++ (GCC) 3.4.2 [FreeBSD] 20040728
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.

Is operator= not polymorphic?

Polymorphism doesn't matter in this example. Polymorphism only matters when
functions are called through a pointer or reference to an object, not when
it is called directly for an object of known type.

Your guess is wrong because you forgot the compiler supplied assignment
operator, which is called for b1 = b2, and which in turn calls A's
(explicit) assignment operator, which prints the message.

HTH
Heinz
 
R

Rolf Magnus

Heiner wrote:

[...]
int main (int)
{
A a;
B b1, b2;
printf("b1 = a: "); b1 = a;
printf("b1 = b2: "); b1 = b2;

The above line calls the compiler generated B& B::eek:perator=(const B&), which
in turn calls the base class's operator=.
printf("b1.test(a): "); b1.test(a);
printf("b1.test(b2): "); b1.test(b2);
return 0;
}
[...]

Is operator= not polymorphic?

It is if you make it virtual (as you did).
 
H

Heiner

What exactly do you want to do?

Thanks for both answers. I have a container class AC containing A
instances. This container can be cloned: it clones all A instances as well
(by calling A::eek:perator=). Now there are derived classes: BC is
derived from AC and B from A. Therefore the container BC contains B
instances (and maybe A instances as well), which can be cloned. I have not
dublicated the clone code of AC, as I thought virtual operator= will do
the magic. Actually it didn't. So I changed my code to:

#include <stdio.h>

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

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

A & A::eek:perator= (const A & src)
{
printf("A= called, ");
return * this;
}

A & B::eek:perator= (const A & src)
{
printf("B.1= called, ");
A::eek:perator=(src);
const B * srcB = dynamic_cast<const B *>(& src);
if (srcB)
printf("B.2= called, ");
return * this;
}

B & B::eek:perator= (const B & src)
{
printf("B.3= called, ");
operator=((const A &)src);
return * this;
}

int main (int)
{
A a;
B b1, b2;
printf("b1 = a: "); b1 = a;
printf("\nb1 = b2: "); b1 = b2;
A* a1 = & b1, *a2 = &b2;
printf("\n*a1 = *a2: "); *a1 = *a2;
printf("\n");
return 0;
}


which now gives:

7of9# gmake && ./test
g++ main.cpp -o test
b1 = a: B.1= called, A= called,
b1 = b2: B.3= called, B.1= called, A= called, B.2= called,
*a1 = *a2: B.1= called, A= called, B.2= called,

I think that is how it should be implemented.
Or is there a dynamic_cast free solution as well?


Heiner
(e-mail address removed)
Remove the nospam to get my real address
 
F

Fraser Ross

class B : public A
{
public:
virtual A & operator= (const A &);
virtual void test(const A &);
};


Is there any reason at any time for writing an assignment operator that
return a reference to a class not of the type the function is within?

The example appears to show a compiler bug with BCB6.

Fraser.
 
R

Rolf Magnus

Heiner said:
Thanks for both answers. I have a container class AC containing A
instances.
Instances?

This container can be cloned: it clones all A instances as well
(by calling A::eek:perator=). Now there are derived classes: BC is
derived from AC and B from A. Therefore the container BC contains B
instances (and maybe A instances as well), which can be cloned.

If it stores instances, no polymorphism is involved.
I have not dublicated the clone code of AC, as I thought virtual operator=
will do the magic. Actually it didn't.

An object cannot change its class during its life time. So the assignment
operator cannot transform an A object into a B object.
So I changed my code to:

#include <stdio.h>

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

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

A & A::eek:perator= (const A & src)
{
printf("A= called, ");
return * this;
}

A & B::eek:perator= (const A & src)
{
printf("B.1= called, ");
A::eek:perator=(src);
const B * srcB = dynamic_cast<const B *>(& src);
if (srcB)
printf("B.2= called, ");
return * this;
}

So if the object provided on the right hand side is not a B, the assignment
operator does in fact nothing. So why have that operator at all? It would
be better to get an error if you try that instead of silently ignoring it.
B & B::eek:perator= (const B & src)
{
printf("B.3= called, ");
operator=((const A &)src);
return * this;
}
I think that is how it should be implemented.

Well, if you want several objects of different types in one container, you
shouldn't store instances but pointers to them in the container. Then, and
only then, can you use polymorphism.
 
H

Heiner

Instances?
I use the term instance for any usage of a class. So A a creates an
instance and A* a = new A as well. If the term is wrong, sorry.

Maybe "pointer of reference to an instance" is more correctly
Well, if you want several objects of different types in one container, you
shouldn't store instances but pointers to them in the container. Then, and
only then, can you use polymorphism.

That's what I wanted to discuss. My container AC contains pointers to
instances of A, and so do BC with B, while BC derives from AC and B from
A. A::eek:perator= is virtual, so that the cloning code within AC should work
for BC instances as well.

I guess, I know now how to do it. Thanks again.

Heiner
(e-mail address removed)
Remove the nospam to get my real address
 
H

Heiner

Is there any reason at any time for writing an assignment operator that
return a reference to a class not of the type the function is within?

Yes, see the rest of the discussion. As B derives from A I wanted to
overwrite A's operator=. Therefore I have to use the same signature.
The example appears to show a compiler bug with BCB6.

Actually it only showed my misunderstanding of C++ default method
generation. If a class X has no X::eek:perator=(const X &), the compiler
creates one. That's what happend here. With both operator= defined, my
example worked (see other posts, please)


Heiner
 
F

Fraser Ross

Yes, see the rest of the discussion. As B derives from A I wanted to
overwrite A's operator=. Therefore I have to use the same signature.

You can't overload with a different return type alone and therefore you
can override with a different return type alone. I think you have an
unwanted way of writing a return type for assignment operators. Noone
has given any reason for writing them as such yet.

Fraser.
 
H

Heiner

You can't overload with a different return type alone and therefore you
can override with a different return type alone. I think you have an
unwanted way of writing a return type for assignment operators. Noone
has given any reason for writing them as such yet.

Sure. The scenario is as follows: I have a class A. And I have a factory
class AC, which creates A's and holds them. If I call a method to copy the
contents of one AC (call it ac1) into another (call it ac2), ac2 deletes
all its A's and creates a bunch of new one. Afterwards it calls operator=
for every new A, passing an A from ac1. As the result, ac2 is a deep clone
of ac1. The actual copying is done by A::eek:perator=(const A&).

So AC looks like:

class AC
{
public:
void cloneFrom(const AC *);
virtual A* createObject();
};

with the implementation

void A::cloneFrom(const AC * source)
{
delete all A's stored in this
for each a of type A in source
{
A * a2 = createObject();
* a2 = * a;
add a2 to this
}
}

Okay? A simple factory pattern combined with a container plus a deep copy
method


Now I have a similar set of classes: B derives from A and BC from AC. BC
is the factory of B. Now I would like to clone bc1 (of type BC) into bc2.
What method do I need to write in BC and B?

BC::createObject? Sure, thats how abstract factories work

BC::cloneForm()? No! I can use the method from AC. AC::cloneFrom calls the
virtual createObject() method, which returns a B instance now. Then it
calls operator= on each A. As this is virtual, the right copy operator is
called.

Last last sentence is the key in the problem! How do I have to design A
and B?

Answer:

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

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


A is clear. Please note, that there are 2 operator= in B! Why?

B::eek:perator= (const B &): thats the natural copy operator for class B. In
my original question I have missed it and the default copy operator
created by the compiler caused my initial problems. The implementation of
this operator is:

B & B::eek:perator= (const B & src)
{
operator=((const A &)src);
return * this;
}

it just calls the other operator=:

B::eek:perator= (const A &) Why this? It is derived from A. It has the same
signature as A::eek:peartor= (but different from the frist operator=, so no
compiler error). If my AC::cloneFrom works with pointers to B instances,
the internal *a2 = *a calls A::eek:perator= (const A &). So without my
second operator=, B will never be copied correctly!

I hope my problem and the solution and the need for 2 operator= in one
class is now more clear.

Heiner
(e-mail address removed)
Remove the nospam to get my real address
 
R

Rolf Magnus

Heiner said:
Sure. The scenario is as follows: I have a class A. And I have a factory
class AC, which creates A's and holds them. If I call a method to copy the
contents of one AC (call it ac1) into another (call it ac2), ac2 deletes
all its A's and creates a bunch of new one. Afterwards it calls operator=
for every new A, passing an A from ac1.

Why doesn't it create the new As using the copy constructor?
As the result, ac2 is a deep clone of ac1. The actual copying is done by
A::eek:perator=(const A&).
Now I have a similar set of classes: B derives from A and BC from AC. BC
is the factory of B. Now I would like to clone bc1 (of type BC) into bc2.
What method do I need to write in BC and B?

BC::createObject? Sure, thats how abstract factories work

BC::cloneForm()? No! I can use the method from AC. AC::cloneFrom calls the
virtual createObject() method, which returns a B instance now. Then it
calls operator= on each A. As this is virtual, the right copy operator is
called.

Ah, now I see why you need the virtual operator=. Actually, what I'd do is
put the copying logic to the class A and not the container. I'd add a
virtual clone() member function to A:

virtual A* clone() const
{
return new A(*this);
}

Then in B, you override it:

virtual B* clone() const
{
return new B(*this);
}

And then your container's cloneFrom() function would look like:

void AC::cloneFrom(const AC * source)
{
delete all A's stored in this
for each a of type A in source
{
A * a2 = a->clone();
add a2 to this
}
}
 
H

Heiner

Why doesn't it create the new As using the copy constructor?

Because the A's created by a factory.
Ah, now I see why you need the virtual operator=. Actually, what I'd do is
put the copying logic to the class A and not the container. I'd add a
virtual clone() member function to A:

virtual A* clone() const
{
return new A(*this);
}

Then in B, you override it:

virtual B* clone() const
{
return new B(*this);
}

And then your container's cloneFrom() function would look like:

void AC::cloneFrom(const AC * source)
{
delete all A's stored in this
for each a of type A in source
{
A * a2 = a->clone();
add a2 to this
}
}

That's a good point, which I will consider. A disadvantage might be, that
later on I might need to copy into an existing A (operator=). But I guess,
a proper design of A, including operator=, copy constructor and clone can
and will avoid any double coding.

Another point is, that all of my A's know their factory. So clone actually
requires 2 arguments (cloned object and target container). In my solution
A is only created by the factory (private default constructor) and
therefore knows its factory.

On the other hand the beauty of your approach is, that it does not need a
dynamic_cast! I will think about it.

Thanks

Heiner
(e-mail address removed)
Remove the nospam to get my real address
 
T

Thomas J. Gritzan

Heiner said:
BC::cloneForm()? No! I can use the method from AC. AC::cloneFrom calls the
virtual createObject() method, which returns a B instance now. Then it
calls operator= on each A. As this is virtual, the right copy operator is
called.

This won't work unless you make sure, that a BC won't store As and a AC
won't store Bs. You can't change the type of an object during its lifetime:

A a;
B b;

b = a;

The object "b" still is of type B.

So think about the clone() function approach and maybe an attach()
function to move the object into another factory/container.

Thomas
 
H

Heiner

This won't work unless you make sure, that a BC won't store As and a AC
won't store Bs. You can't change the type of an object during its lifetime:

A a;
B b;

b = a;

The object "b" still is of type B.

Sure. But this problem (the mixed types within the container) does not
occur, as the objects are created by factories only. And the AC and BC are
the container and the factory combined. So the only way to create an A is
to call AC::createObject. By this, the types are never mixed.
So think about the clone() function approach and maybe an attach()
function to move the object into another factory/container.

I thought about it and I found a problem: As I work with factories and as
I do NOT want to have mixed types, I have to pass the factory into the
clone:

virtual A * A::clone(AC * factory)
{
A * a = factory->createObject();
do all the copying here
}

But how should I implement this for B?

virtual A * B::clone(AC * factory)
{
A * a = factory->newObject();
do all the copying here
}

But what to copy? Only the members from A? That is not sufficient, if
factory is an instance of BC. All the members of B? This will fail, if
factory is an instance of AC. So I have to dynamic_cast here and therefore
the clone approach has the same advantage or disadvantage as my double
operator= solution. I just renamed the second operator= into

virtual void copyFrom(A *);

so that I now have one operator= for each class and one copyFrom. Inside I
also have to dynamic_cast to find out, what to copy.


Heiner
(e-mail address removed)
Remove the nospam to get my real address
 
H

Heiner

/*
In case someone is still reading it: A summary

I need a factory (AC), creating objects (A). The factory also holds all
objects it has created. AC also has a copyFrom(AC *) method: it removes
all object created so far and replaces it with cloned objects from the
passecd factory.

As this is (nearly) an abstract factory pattern, there is also a B,
derived from A with a factory BC. As there is no code dublication: BC has
no copyFrom method. It uses AC's. In order to work, both factories have a
virtual createObject method, which creates an A or B. Furthermore A and B
also have a virtual copyFrom method, which copies their contents (similar
to operator=). Now AC's copyFrom just calls createObject, to create the
clones and on each of them copyFrom(A *), to clone the objects contents.
This also works, if called from a BC, as everything is virtual.

In a first aproach I named A's and B's copyFrom operator=, resulting in
two operator= for class B. This caused some confusion. So this version
here uses copyFrom instead of operator=.

A disadvantage of this aproach is the need of calling dynamic_cast within
the copyFrom, as it is possible, to call AC::copyFrom(AC*) with a BC
instance. There was a proposal to use

virtual A * A::clone()

instead; but as a factory is reqiured, this must be a

virtual A * A::clone(AC *),

and the dynamic_cast is back. If someone has a better solution without casts....

The attached example demonstrates it. It is bad in several aspects
(memory leaks, no privates, ...) but it was designed to be as short as
possible. If called, it gives:

output:

7of9# gmake && ./test
g++ main.cpp -o test
A: 69
B::copyFrom called
A::copyFrom called
B: 69, 42
B::copyFrom called
A::copyFrom called
B::copyFrom called with a B
B: 69, 42
A::copyFrom called
A: 69

Heiner


*/

#include <stdio.h>

class A { // objects to work with
public:
int a;
A & operator= (const A &);
virtual void copyFrom (const A *); // do the actual copying
virtual void whoAmI() const {printf("A: %d\n", a);}
};

class AC { // factory and container. no destructor (memory leak!)
public:
A * oneObject; // in this example just one object
AC() : oneObject(NULL){}
virtual A * getObject() const {return oneObject;}
virtual A * createObject(); // create a new object
void copyFrom(const AC * src); // clone all objects from src!
};

class B : public A { // another object derived from A
public:
int b;
virtual B & operator= (const B &);
virtual void copyFrom (const A *);
virtual void whoAmI() const {printf("B: %d, %d\n", a, b);}
};

class BC : public AC { // factory and container for B
public:
virtual A * createObject();
B * getObjectB() const {return (B *)oneObject;} // convenience
};


A & A::eek:perator= (const A & src) { // call copyFrom to do the job
copyFrom(& src);
return * this;
}

void A::copyFrom (const A * src) { // copy contents
printf("A::copyFrom called\n");
a = src->a;
}

A * AC::createObject() { // create A instance
A * result = new A;
return oneObject = result; // store object
}

void AC::copyFrom(const AC * a) {
oneObject = NULL; // delete own "container" (memory leak!)
if (a->oneObject) { // s.th to clone?
oneObject = createObject(); // create clone
*oneObject = *a->oneObject; // copy data. Works for B's as well!
}
}

void B::copyFrom (const A * src) { // copy B's contents
printf("B::copyFrom called\n");
A::copyFrom(src); // call base class
const B * srcB = dynamic_cast<const B *>(src);
if (srcB) { // if src is a B, copy additional stuff:
printf("B::copyFrom called with a B\n");
b = srcB->b;
}
}

B & B::eek:perator= (const B & src) { // not used here
copyFrom(& src);
return * this;
}

A * BC::createObject() { // same as AC:: createObject
A * result = new B;
return oneObject = result;
}


int main (int)
{
AC ac; // 1st factory
A * a = ac.createObject(); // create an object
a->a = 69;
a->whoAmI();
BC bc; //2nd factory
bc.copyFrom(& ac); // a is now copied into a B!
B * b = bc.getObjectB(); // proof:
b->b = 42;
b->whoAmI(); // indeed: a B!
BC bc2; // 3rd factory
bc2.copyFrom(& bc); // b is copied into a B
bc2.getObject()->whoAmI(); // indeed
AC ac2; // fourth factory
ac2.copyFrom(& bc); // now b is an A again!
ac2.getObject()->whoAmI(); // indeed
return 0;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,562
Members
45,038
Latest member
OrderProperKetocapsules

Latest Threads

Top