On polymorphism, pointers, and the like...

E

ex_ottoyuhr

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?
 
A

Alf P. Steinbach

* ex_ottoyuhr:
Can anyone recommend a better way [than polymorphic assignment]?

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)?
 
E

ex_ottoyuhr

Alf said:
* ex_ottoyuhr:
Can anyone recommend a better way [than polymorphic assignment]?

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

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...
Also, use smart-pointers such as instead of raw pointers.

Any particular "brand" you'd recommend?
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)?

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.
 
P

Phil Staite

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...
 
A

Axter

ex_ottoyuhr said:
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?

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/algorithms/general/article.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.
 
A

Alf P. Steinbach

* ex_ottoyuhr:
* ex_ottoyuhr:
Can anyone recommend a better way [than polymorphic assignment]?

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

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...

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.

Any particular "brand" you'd recommend?

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

ex_ottoyuhr

Axter wrote:
I recommend you use a clone smart pointer, which will correctly copy
your derived type.
<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.
 
K

Kai-Uwe Bux

ex_ottoyuhr said:
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;

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::eek:stream & dump ( std::eek:stream & ostr ) const {
return( ostr << "base" );
}

};

std::eek:stream & operator<< ( std::eek:stream & 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::eek:stream & dump ( std::eek:stream & 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::eek:stream & operator<< ( std::eek:stream & 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
 
E

ex_ottoyuhr

Kai-Uwe Bux wrote:
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):

<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?
 
E

ex_ottoyuhr

Axter said:
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/algorithms/general/article.php/c10407/

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?
 
K

Kai-Uwe Bux

ex_ottoyuhr said:
Kai-Uwe Bux wrote:


<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?

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
 
A

Axter

ex_ottoyuhr said:
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?

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.
 

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

Forum statistics

Threads
474,431
Messages
2,571,679
Members
48,796
Latest member
Greg L.

Latest Threads

Top