Connecting Tech Pros Worldwide Forums | Help | Site Map

On polymorphism, pointers, and the like...

ex_ottoyuhr
Guest
 
Posts: n/a
#1: Nov 22 '05
I have a situation more or less as follows, and it's causing me no end
of trouble; I'd appreciate anyone's advice on the matter...

Given these classes:

class BedrockCitizen { ... };

class Fred : public BedrockCitizen { ... };
class Wilma : public BedrockCitizen { ... };
class Barney : public BedrockCitizen { ... };
..
..
..

class TownOfBedrock { ... };

What I'd like to be able to do is to create a means of storing any
number of BedrockCitizens of any of the child classes in class
TownOfBedrock, without too much inconvenience. Unfortunately, while
that can be done by having a data structure indexing
pointers-to-BedrockCitizen, and downcasting as appropriate, there's a
heck of a lot of inconvenience involved.

At the present, I'm badly bogged down in the attendant memory
management in classes using BedrockCitizens; in particular, I've just
realized that my operator= and copy constructor are going to have to
use some sort of RTTI plus switch statements to allocate the right sort
of class to copy these things successfully -- which strikes me as
neither safe, extensible, nor prudent. In plainer language, I think
that TownOfBedrock's operator= is going to have to contain something
more or less like this:

(Assume Census is a vector<BedrockCitizen*>)

typedef std::vector<BedrockCitizen*>::iterator CitizenIt;
BedrockCitizen* aBC;
for ( CitizenIt anIt, = copyFrom.Census.begin(); anIt !=
copyFrom.Census.end(); anIt++ ) {
if ( typeid(*anIt) == typeid(Fred) ) {
aBC = new Fred;
// copy the old Fred's data into the new one.
Census.push_back(aBC);
} /* and so on for the other classes */
}

But this is an approach equally hideous and time-consuming. Can anyone
recommend a better way?


Alf P. Steinbach
Guest
 
Posts: n/a
#2: Nov 22 '05

re: On polymorphism, pointers, and the like...


* ex_ottoyuhr:[color=blue]
>
> Can anyone recommend a better way [than polymorphic assignment]?[/color]

Disable assignment for all polymorphic classes; you do that by declaring
a private assignment operator, which you don't have to implement.

Also, use smart-pointers such as instead of raw pointers.

Finally, I don't think Fred and Wilma qualify as classes: how many
Fred's do you want walking around (not to mention Wilma's)?

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
ex_ottoyuhr
Guest
 
Posts: n/a
#3: Nov 22 '05

re: On polymorphism, pointers, and the like...



Alf P. Steinbach wrote:[color=blue]
> * ex_ottoyuhr:[color=green]
> >
> > Can anyone recommend a better way [than polymorphic assignment]?[/color]
>
> Disable assignment for all polymorphic classes; you do that by declaring
> a private assignment operator, which you don't have to implement.[/color]

Would that be declaring a private operator= for TownOfBedrock, or
BedrockCitizen? If the former, I suppose I can live without assignment
for these things, in most cases...
[color=blue]
> Also, use smart-pointers such as instead of raw pointers.[/color]

Any particular "brand" you'd recommend?
[color=blue]
> Finally, I don't think Fred and Wilma qualify as classes: how many
> Fred's do you want walking around (not to mention Wilma's)?[/color]

I guess I used rather bad examples there. The thing I'm getting this
problem from is an SF game, where I'm modelling different systems on a
spacecraft with structs inheriting from a "Ship's System" parent. All
of them have certain things in common -- volume taken up, crew
required, operating efficiency, power drain and so on -- while they
also have various sorts of data unique to the specific system.

Thanks a lot for the quick reply.

Phil Staite
Guest
 
Posts: n/a
#4: Nov 22 '05

re: On polymorphism, pointers, and the like...


Consider a "virtual constructor" or clone method. Something like:

class Citizen
{
public:
virtual ~Citizen() {}

virtual Citizen* clone() const = 0;

....

};


class StandUpGuy : public Citizen
{
public:

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

};


Note, really, really work on your interfaces and try to avoid downcasting...


Axter
Guest
 
Posts: n/a
#5: Nov 22 '05

re: On polymorphism, pointers, and the like...


ex_ottoyuhr wrote:[color=blue]
> I have a situation more or less as follows, and it's causing me no end
> of trouble; I'd appreciate anyone's advice on the matter...
>
> Given these classes:
>
> class BedrockCitizen { ... };
>
> class Fred : public BedrockCitizen { ... };
> class Wilma : public BedrockCitizen { ... };
> class Barney : public BedrockCitizen { ... };
> .
> .
> .
>
> class TownOfBedrock { ... };
>
> What I'd like to be able to do is to create a means of storing any
> number of BedrockCitizens of any of the child classes in class
> TownOfBedrock, without too much inconvenience. Unfortunately, while
> that can be done by having a data structure indexing
> pointers-to-BedrockCitizen, and downcasting as appropriate, there's a
> heck of a lot of inconvenience involved.
>
> At the present, I'm badly bogged down in the attendant memory
> management in classes using BedrockCitizens; in particular, I've just
> realized that my operator= and copy constructor are going to have to
> use some sort of RTTI plus switch statements to allocate the right sort
> of class to copy these things successfully -- which strikes me as
> neither safe, extensible, nor prudent. In plainer language, I think
> that TownOfBedrock's operator= is going to have to contain something
> more or less like this:
>
> (Assume Census is a vector<BedrockCitizen*>)
>
> typedef std::vector<BedrockCitizen*>::iterator CitizenIt;
> BedrockCitizen* aBC;
> for ( CitizenIt anIt, = copyFrom.Census.begin(); anIt !=
> copyFrom.Census.end(); anIt++ ) {
> if ( typeid(*anIt) == typeid(Fred) ) {
> aBC = new Fred;
> // copy the old Fred's data into the new one.
> Census.push_back(aBC);
> } /* and so on for the other classes */
> }
>
> But this is an approach equally hideous and time-consuming. Can anyone
> recommend a better way?[/color]

I recommend you use a clone smart pointer, which will correctly copy
your derived type.
Moreover, I recommend you use a vector of clone pointers.
Check out the following links:

http://code.axter.com/copy_ptr.h

For usage, check out the following link:
http://www.codeguru.com/Cpp/Cpp/algo...le.php/c10407/

The above link uses a clone_ptr smart pointer, but that is just an
older version of the copy_ptr class.
http://code.axter.com/clone_ptr.h

You should also consider using a COW (Copy On Write) smart pointer.
http://code.axter.com/cow_ptr.h

It's more efficient then a regular clone smart pointer.

Alf P. Steinbach
Guest
 
Posts: n/a
#6: Nov 22 '05

re: On polymorphism, pointers, and the like...


* ex_ottoyuhr:[color=blue]
>
> Alf P. Steinbach wrote:[color=green]
> > * ex_ottoyuhr:[color=darkred]
> > >
> > > Can anyone recommend a better way [than polymorphic assignment]?[/color]
> >
> > Disable assignment for all polymorphic classes; you do that by declaring
> > a private assignment operator, which you don't have to implement.[/color]
>
> Would that be declaring a private operator= for TownOfBedrock, or
> BedrockCitizen? If the former, I suppose I can live without assignment
> for these things, in most cases...[/color]

Any class designed for polymorphic usage.

If you don't disable assignment you'll either have slicing problems
(scrambling your data good, in the worst case), or you'll have to
implement polymorphic assignment, which means run-time type checking and
ditto possible run-time errors, not to mention the complexity &
fragility of the code.

Implement copying by providing cloning operations, if necessary.

[color=blue][color=green]
> > Also, use smart-pointers such as instead of raw pointers.[/color]
>
> Any particular "brand" you'd recommend?[/color]

Depends. For shared objects, boost::shared_ptr. For non-shared
objects, e.g. std::auto_ptr.

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
Axter
Guest
 
Posts: n/a
#7: Nov 22 '05

re: On polymorphism, pointers, and the like...


Phil Staite wrote:[color=blue]
> Consider a "virtual constructor" or clone method. Something like:
>
> class Citizen
> {
> public:
> virtual ~Citizen() {}
>
> virtual Citizen* clone() const = 0;
>[/color]

Using a method that requires the class to have a clone method, creates
more work, and more maintenance.

I recommend using a good clone smart pointer that doesn't require the
class to have a clone method.
Example:
http://code.axter.com/copy_ptr.h
http://code.axter.com/cow_ptr.h

ex_ottoyuhr
Guest
 
Posts: n/a
#8: Nov 22 '05

re: On polymorphism, pointers, and the like...



Axter wrote:
<Snip>[color=blue]
> I recommend you use a clone smart pointer, which will correctly copy
> your derived type.[/color]
<Snip links>

Thanks a lot for those. I take it the state of the art is a lot more
advanced than what I know so far; I appreciate your advice, and that of
everyone in the thread.

Kai-Uwe Bux
Guest
 
Posts: n/a
#9: Nov 22 '05

re: On polymorphism, pointers, and the like...


ex_ottoyuhr wrote:
[color=blue]
> I have a situation more or less as follows, and it's causing me no end
> of trouble; I'd appreciate anyone's advice on the matter...
>
> Given these classes:
>
> class BedrockCitizen { ... };
>
> class Fred : public BedrockCitizen { ... };
> class Wilma : public BedrockCitizen { ... };
> class Barney : public BedrockCitizen { ... };
> .
> .
> .
>
> class TownOfBedrock { ... };
>
> What I'd like to be able to do is to create a means of storing any
> number of BedrockCitizens of any of the child classes in class
> TownOfBedrock, without too much inconvenience. Unfortunately, while
> that can be done by having a data structure indexing
> pointers-to-BedrockCitizen, and downcasting as appropriate, there's a
> heck of a lot of inconvenience involved.
>
> At the present, I'm badly bogged down in the attendant memory
> management in classes using BedrockCitizens; in particular, I've just
> realized that my operator= and copy constructor are going to have to
> use some sort of RTTI plus switch statements to allocate the right sort
> of class to copy these things successfully -- which strikes me as
> neither safe, extensible, nor prudent. In plainer language, I think
> that TownOfBedrock's operator= is going to have to contain something
> more or less like this:
>
> (Assume Census is a vector<BedrockCitizen*>)
>
> typedef std::vector<BedrockCitizen*>::iterator CitizenIt;
> BedrockCitizen* aBC;
> for ( CitizenIt anIt, = copyFrom.Census.begin(); anIt !=
> copyFrom.Census.end(); anIt++ ) {
> if ( typeid(*anIt) == typeid(Fred) ) {
> aBC = new Fred;[/color]

At this point it appears that you want to copy the BedrockCitizen* in a
manner that preserves its true dynamic type. Such a thing is usually
accomplished using a virtual clone() method in BedrockCitizen. I think this
is called virtual constructors and should be covered in the FAQ.

An alternative is to use a smart pointer with deep copy semantics. There are
several ways ways of writing such a smart pointer. The following is just a
proof of concept (the class has just about 100 lines of code an can serve
as an illustration of what happens):


/*
| - Upon construction, copy_ptr<T> takes pointee ownership of
| a D* where D is a type derived from T. (compile type check)
| - In any copy construction or assignment a copy of the
| pointee is created. There should be no slicing.
| - Upon destruction the pointee is destroyed.
| - Intended use is within STL containers.
| - If T does not have a virtual destrucctor, copy_ptr<T>
| cannot be used polymorphically (compile time check).
*/

// we swap:
#include <algorithm>

// The clone_traits function:
template < typename T, typename D >
T * clone ( T * ptr ) {
return( new D ( *( static_cast<D*>( ptr ) ) ) );
}

template < typename T >
T * simple_clone ( T * ptr ) {
return( new T ( *ptr ) );
}


// forward declarations:
template < typename T >
class copy_ptr;

template < typename T >
void swap ( copy_ptr< T > &, copy_ptr< T > & );


// implementation:
template < typename T >
class copy_ptr {

friend void swap<> ( copy_ptr<T> &, copy_ptr<T> & );

/*
The idea is that in addition to a pointer, we also need
a pointer to the appropriate clone_traits function.
*/
T * raw_ptr;
T * ( *clone_fct ) ( T * );

public:

copy_ptr ( void )
: raw_ptr ( new T )
, clone_fct( simple_clone<T> )
{}

copy_ptr ( T * ptr )
: raw_ptr ( ptr )
, clone_fct ( simple_clone<T> )
{}

template < typename D >
copy_ptr ( D * ptr )
: raw_ptr ( ptr )
, clone_fct ( clone<T,D> )
{}

// copy construction clone_traitss:
copy_ptr ( copy_ptr const & other )
: raw_ptr ( other.clone_fct( other.raw_ptr ) )
, clone_fct ( other.clone_fct )
{}

// destruction frees the pointee
~copy_ptr ( void ) {
delete( raw_ptr );
}

// assignment reduces to copy construction:
copy_ptr & operator= ( copy_ptr const & other ) {
copy_ptr dummy ( other );
swap( *this, dummy );
return( *this );
}

T const * operator-> ( void ) const {
return( raw_ptr );
}

T * operator-> ( void ) {
return( raw_ptr );
}

T const & operator* ( void ) const {
return( *raw_ptr );
}

T & operator* ( void ) {
return( *raw_ptr );
}

}; // copy_ptr<T>

template < typename T >
void swap ( copy_ptr< T > & p, copy_ptr< T > & q ) {
std::swap( p.raw_ptr, q.raw_ptr );
std::swap( p.clone_fct, q.clone_fct );
}


// a sanity check:

#include <iostream>

struct Base {

Base ( void ) {
std::cout << "base is born.\n";
}

Base ( Base const & other ) {
std::cout << "base is copied.\n";
}

virtual ~Base () {
std::cout << "base dies.\n";
}

virtual
std::ostream & dump ( std::ostream & ostr ) const {
return( ostr << "base" );
}

};

std::ostream & operator<< ( std::ostream & ostr,
Base const & obj ) {
return( obj.dump( ostr ) );
}

struct Derived : public Base {

Derived ( void ) {
std::cout << "derived is born.\n";
}

Derived ( Derived const & other )
: Base ( other )
{
std::cout << "derived is copied.\n";
}

virtual ~Derived () {
std::cout << "derived dies.\n";
}

virtual
std::ostream & dump ( std::ostream & ostr ) const {
return( ostr << "derived" );
}

};

struct NotDerived {

NotDerived ( void ) {
std::cout << "not-derived is born.\n";
}

NotDerived ( NotDerived const & other )
{
std::cout << "not-derived is copied.\n";
}

virtual ~NotDerived () {
std::cout << "not-derived dies.\n";
}

};

struct BadBase {};
struct BadDerived : public BadBase {};

std::ostream & operator<< ( std::ostream & ostr, NotDerived const & obj ) {
return( ostr << "not-derived" );
}

int main ( void ) {
copy_ptr< Base > a_ptr;
copy_ptr< Base > b_ptr ( new Derived() );

std::cout << '\n' << *a_ptr << " " << *b_ptr << "\n\n";

a_ptr = b_ptr;
std::cout << '\n' << *a_ptr << " " << *b_ptr << "\n\n";

copy_ptr< int > i_ptr;

// compile time errors:
// copy_ptr< Base > x_ptr ( new NotDerived() );
// copy_ptr< int > j_ptr ( new Base() );
// copy_ptr< BadBase > bad_ptr ( new BadDerived () );

}


Also, Axter has a version of a copy/clone pointer at:

http://code.axter.com/clone_ptr.h
http://code.axter.com/copy_ptr.h



Best

Kai-Uwe Bux


ex_ottoyuhr
Guest
 
Posts: n/a
#10: Nov 22 '05

re: On polymorphism, pointers, and the like...



Kai-Uwe Bux wrote:
<Snip>[color=blue]
> At this point it appears that you want to copy the BedrockCitizen* in a
> manner that preserves its true dynamic type. Such a thing is usually
> accomplished using a virtual clone() method in BedrockCitizen. I think this
> is called virtual constructors and should be covered in the FAQ.
>
> An alternative is to use a smart pointer with deep copy semantics. There are
> several ways ways of writing such a smart pointer. The following is just a
> proof of concept (the class has just about 100 lines of code an can serve
> as an illustration of what happens):[/color]

<Snip great lengths of code>

All that I'm really trying to do with this is create a class which can
store and manipulate objects of any child of a given parent class -- or
rather, a given parent _struct_ -- without crashing the program at any
point in the process; is there a simpler way to do it than all this?
Would it be better, do you think, to just create gigantic parent
structs which could accomodate all variables possibly needed by any of
what would otherwise be their children?

ex_ottoyuhr
Guest
 
Posts: n/a
#11: Nov 22 '05

re: On polymorphism, pointers, and the like...



Axter wrote:[color=blue]
> I recommend you use a clone smart pointer, which will correctly copy
> your derived type.
> Moreover, I recommend you use a vector of clone pointers.
> Check out the following links:
>
> http://code.axter.com/copy_ptr.h
>
> For usage, check out the following link:
> http://www.codeguru.com/Cpp/Cpp/algo...le.php/c10407/[/color]

One other question. I'm declaring the equivalent of an
std::map<copy_ptr<BedrockCitizen> >, and am getting an error to the
effect of "no appropriate default constructor available". Have I
misunderstood the class' use?

Kai-Uwe Bux
Guest
 
Posts: n/a
#12: Nov 22 '05

re: On polymorphism, pointers, and the like...


ex_ottoyuhr wrote:
[color=blue]
>
> Kai-Uwe Bux wrote:
> <Snip>[color=green]
>> At this point it appears that you want to copy the BedrockCitizen* in a
>> manner that preserves its true dynamic type. Such a thing is usually
>> accomplished using a virtual clone() method in BedrockCitizen. I think
>> this is called virtual constructors and should be covered in the FAQ.
>>
>> An alternative is to use a smart pointer with deep copy semantics. There
>> are several ways ways of writing such a smart pointer. The following is
>> just a proof of concept (the class has just about 100 lines of code an
>> can serve as an illustration of what happens):[/color]
>
> <Snip great lengths of code>
>
> All that I'm really trying to do with this is create a class which can
> store and manipulate objects of any child of a given parent class -- or
> rather, a given parent _struct_ -- without crashing the program at any
> point in the process; is there a simpler way to do it than all this?
> Would it be better, do you think, to just create gigantic parent
> structs which could accomodate all variables possibly needed by any of
> what would otherwise be their children?[/color]

This is a pretty broad description of the problem. You need to make some
design decisions:

a) Do you want to use polymorphism or not? The alternative to polymorphism
is to have a big struct that has all members that possible derived classes
could feature. In using this bloated struct, you would just ignore most of
the field most of the time.

pros: the struct has well defined copy semantics and can be stored without
hassle in standard containers.

cons: the size of the class and its interface will be bloated. Moreover,
code will be hard to maintain as the programmer has to understand at each
point which members are meaningfull and which are not.

b) If you opt for true polymorphism, you are bound to use pointers, i.e.,
instead of just using variables of type BedrockCitizen, you have to use
BedrockCitizen*. This creates all sorts of hassle because of lifetime
management. Also, all of a sudden the semantics of an assignment changes:

BedrockCitizen* a_ptr;
BedrockCititen* b_ptr;


...

a_ptr = b_ptr;
// now a_ptr and b_ptr own the same object. Presumably, b_ptr has lost a
// reference to some previously owned object, ...
// as opposed to
*a_ptr = *b_ptr;

Smart pointers are meant to help you dealing with these hassles. There are
three natural smart pointers:

auto_ptr<T> : strict ownership with ownership transfer upon assignment.
shared_ptr<T> : shared ownership with ownership transfer upon assignment.
copy_ptr<T> : strict ownership with copy semantics.
[Note: shared ownership with copy semantics doesn't make sense.]

The latter two can be used with standard containers. Which one works for you
depends on the problem. Roughly: shared_ptr<T> mimics Java objects
(reference semantics) whereas copy_ptr<T> mimics C++ objects (value
semantics).


Best

Kai-Uwe Bux
Axter
Guest
 
Posts: n/a
#13: Nov 22 '05

re: On polymorphism, pointers, and the like...



ex_ottoyuhr wrote:[color=blue]
> Axter wrote:[color=green]
> > I recommend you use a clone smart pointer, which will correctly copy
> > your derived type.
> > Moreover, I recommend you use a vector of clone pointers.
> > Check out the following links:
> >
> > http://code.axter.com/copy_ptr.h
> >
> > For usage, check out the following link:
> > http://www.codeguru.com/Cpp/Cpp/algo...le.php/c10407/[/color]
>
> One other question. I'm declaring the equivalent of an
> std::map<copy_ptr<BedrockCitizen> >, and am getting an error to the
> effect of "no appropriate default constructor available". Have I
> misunderstood the class' use?[/color]

A std::map class requires two template arguments. Your above code only
has one.
Try something like one of the following:
std::map<copy_ptr<BedrockCitizen>, int> MyMap1;
std::map<int, copy_ptr<BedrockCitizen> > MyMap2;

I'm not sure if you're trying to map your pointer to some other type,
or map some other type to your pointer.
If you're trying to do something like MyMap1, then you shouldn't have a
problem.
std::map<copy_ptr<AbstractBaseClass>, int> MyMap1;
MyMap1[new Derived_C( "3" )] = 123;
That should compile with no problem.
If you're trying to do something like MyMap2, then that will give you
problems.
I'm not sure, why, but std::map seems to want a default constructor.
I modified the copy_ptr smart class so it should now work with the
MyMap2 version.
If you download it again, and try it out, it should work.
http://code.axter.com/copy_ptr.h

I don't like having to give the smart pointer a default constuctor, but
I don't see any other work around.

Closed Thread