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