deep/real copy of derived objects

H

Hicham Mouline

Hello

I have a base struct B from which 2 struct D1 and D2 derive publicly. A 3rd
struct D3 derives from D1.

This is data aggregation.

A "manager" class needs to store copies of these objects when its ctor is
called, the ctor takes as an argument const B&.
How can I take a "real" copy of the argument?
What should I store as a member of "manager"? ideally just a base pointer or
reference?

Regards,
 
F

Francesco S. Carta

Hello

I have a base struct B from which 2 struct D1 and D2 derive publicly. A 3rd
struct D3 derives from D1.

This is data aggregation.

A "manager" class needs to store copies of these objects when its ctor is
called, the ctor takes as an argument const B&.
How can I take a "real" copy of the argument?
What should I store as a member of "manager"? ideally just a base pointer or
reference?

I'm not sure I fully understand your question, so cope with my response.

You're dealing with polymorphic objects and "manager" only receives a
reference to their base, ok so far.

Now you want to create a "real" copy of that argument, and with that I
suppose you mean that if I pass a D3& to it as a B&, then "manager"
should be able to create a D3 copy out of that B& - note the absence of
the ampersand there.

OK, if "manager" can be aware of all that hierarchy and if you're not
concerned with some runtime overhead, then you could interrogate B& for
its real type by dynamically casting it, in turn, to a pointer to each
derived type - it is important to cast to a pointer so that you can
check it for nullity afterwards, whereas casting to another reference
equates to an assertion that would lead to a bad_cast exception in the
failure case (which in turn would make your code unnecessarily more
convoluted).

The other option is to add a clone() method to the whole hierarchy and
going on ignoring the stored clone's type, at the price of having to
delete it at the appropriate moment - while the previous method allows
you storing automatic instances of those copies, but the difference
wouldn't be that signifying as you could store the pointer in a "smart"
way in any case.

I hope the above is clear enough, otherwise I'll post some code to
illustrate my points - assuming I didn't misinterpret your question :)
 
A

Alf P. Steinbach /Usenet

* Hicham Mouline, on 20.08.2010 01:11:
I have a base struct B from which 2 struct D1 and D2 derive publicly. A 3rd
struct D3 derives from D1.

This is data aggregation.

A "manager" class

Oops, that's a design smell (or stench).

Some classes are conceptuelly "doers", like a functor class, a thread class or a
class encapsulating a routine hierarchy.

However, such a "doer" class is characterized by having an operator() or some
method called "execute" or "run" or such, that is, an instance is meant to
provide a service like a function, produced by a member function call.

A "manager" class is not that kind, it's most often just a hodgepodge collection
of unrelated responsibilities and unrelated knowledge snippets.

Think about responsibilities, think about what knowledge is needed for each
responsibility, re-factor as necessary.

needs to store copies of these objects when its ctor is
called, the ctor takes as an argument const B&.
How can I take a "real" copy of the argument?

See the FAQ item about cloning. Heads-up: the FAQ uses the term "virtual
construction". The FAQ is about the only trustworthy source of info that uses
that term for cloning, although some authorities in OO have done that (earlier).

What should I store as a member of "manager"? ideally just a base pointer or
reference?

A smart pointer.

But I think you should get rid of the "manager". ;-)


Cheers & hth.,

- Alf
 
J

Jorgen Grahn

* Hicham Mouline, on 20.08.2010 01:11:

Oops, that's a design smell (or stench).

Some classes are conceptuelly "doers", like a functor class, a thread class or a
class encapsulating a routine hierarchy.

However, such a "doer" class is characterized by having an operator() or some
method called "execute" or "run" or such, that is, an instance is meant to
provide a service like a function, produced by a member function call.

A "manager" class is not that kind, it's most often just a hodgepodge collection
of unrelated responsibilities and unrelated knowledge snippets.

Right. When I see the all-too-frequent "FooManager" classes, their
name usually means "This things owns a lot of Foo instances, but I'm
not sure what other responsibilities it has. It's more important
than a mere container, though!"
Think about responsibilities, think about what knowledge is needed for each
responsibility, re-factor as necessary.

And try to find a class name that fits those responsibilities.

/Jorgen
 
H

Hicham Mouline

Alf P. Steinbach /Usenet said:
* Hicham Mouline, on 20.08.2010 01:11:

Oops, that's a design smell (or stench).

Some classes are conceptuelly "doers", like a functor class, a thread
class or a class encapsulating a routine hierarchy.

However, such a "doer" class is characterized by having an operator() or
some method called "execute" or "run" or such, that is, an instance is
meant to provide a service like a function, produced by a member function
call.

A "manager" class is not that kind, it's most often just a hodgepodge
collection of unrelated responsibilities and unrelated knowledge snippets.

Think about responsibilities, think about what knowledge is needed for
each responsibility, re-factor as necessary.



See the FAQ item about cloning. Heads-up: the FAQ uses the term "virtual
construction". The FAQ is about the only trustworthy source of info that
uses that term for cloning, although some authorities in OO have done that
(earlier).



A smart pointer.

But I think you should get rid of the "manager". ;-)


Cheers & hth.,

- Alf

Thanks,
You are definitely right.
The full story is this:

I have daily measures of different experiment types taken over years.
The "manager" class is called historical_data that contains the list of 20
years of daily measures.
historical_data stores its own experiment object instance. Indeed, these
instances exist only within the scope of historical_data.

class historical_data {
historical_data(const experiment&, begin, end);
historical_data(const historical_data&);
private:
historical_data(const experiment&);
const experiment // what here a const-ref pointer smart pointer ?
std::list<entry> data_; // an entry contains a boost::gregorian::date and
the measure (a set of doubles, same for all experiments)
};

After a 1st iteration, it appeared then that I have different types of
experiments.These experiments are just struct like

struct experiment1 {
double d;
int i;
};

they all share a couple fields which I've put in a base class
"experiment_measure" and specific fields to each.

Different historical datas could be for instance traversed together, or
merged (to get the intersection of identical dates)....
A historical data can be plot, can be viewed.

Should I change historical_data to have 1 different class for each different
experiment?
Should I change the experiment class hierarchy to implement a clone method?

I'll take a look at the FAQ re virtual construction.

Thanks,
 
F

Francesco S. Carta

Thanks,
You are definitely right.

So you're thinking about eliminating "historical_data"? (as you're going
to say below, that's your "manager")
The full story is this:

I have daily measures of different experiment types taken over years.
The "manager" class is called historical_data that contains the list of 20
years of daily measures.
historical_data stores its own experiment object instance. Indeed, these
instances exist only within the scope of historical_data.

So "historical_data" (formerly the "manager") is the only owner of your
"experiment" instances (either directly or indirectly), where
"experiment" should be the former "B" class (the base class) in your "B,
D1, D2, D3" hierarchy above.

In that case you should either keep the "historical_data" or you should
create another container for your "experiment"s.

If the "experiment" instances must be grouped and handled separately, I
think you could keep "historical_data".
class historical_data {
historical_data(const experiment&, begin, end);

What are "begin" and "end"?
historical_data(const historical_data&);
private:
historical_data(const experiment&);
const experiment // what here a const-ref pointer smart pointer ?

A smart pointer, for the details you have given so far, without
completely revolutionizing your design. But read below.
std::list<entry> data_; // an entry contains a boost::gregorian::date and
the measure (a set of doubles, same for all experiments)
};

After a 1st iteration, it appeared then that I have different types of
experiments.These experiments are just struct like

struct experiment1 {
double d;
int i;
};

they all share a couple fields which I've put in a base class
"experiment_measure" and specific fields to each.

I do not understand the above... that's the advantage of posting
complete and meaningful code at the first post: it avoids
misunderstandings and unneeded question/answer exchanges about something
that should be straight and direct C++.
Different historical datas could be for instance traversed together, or
merged (to get the intersection of identical dates)....

These are very important aspects which should be analyzed in their
actual implementation, as above, with straight and direct C++.
A historical data can be plot, can be viewed.

That is secondary, you should separate data _implementation_
(allocation, aggregation, ownership and so on) from data _presentation_
(plotting, view and so on).
Should I change historical_data to have 1 different class for each different
experiment?

I would never do something like that. I would make a template out of it,
before duplicating code by hand.
Should I change the experiment class hierarchy to implement a clone method?

It depends on the amount of different experiments and how much they
differ from each other (as for their actual implementation).

I'd really like to see more of what you have coded so far, I like to
create code based on other's implementations and targets, just give me
some more details and some more code to munch and I would show you a
couple of different approaches - if you are interested, of course... the
subject is quite interesting for me.
 
H

Hicham Mouline

Alf P. Steinbach /Usenet said:
* Hicham Mouline, on 20.08.2010 01:11:
See the FAQ item about cloning. Heads-up: the FAQ uses the term "virtual
construction". The FAQ is about the only trustworthy source of info that
uses that term for cloning, although some authorities in OO have done that
(earlier).
Is there a way to obtain this clone() function
1.without changing the class hierarchy at all?
2.a smaller change than what's presented in the FAQ

regards,
 
K

Kai-Uwe Bux

Hicham said:
Is there a way to obtain this clone() function
1.without changing the class hierarchy at all?
2.a smaller change than what's presented in the FAQ

To some degree: instead of making the class clonable, you can use smart
pointers with deep copy semantics. Search the archives for "clone_ptr" or
"copy_ptr" and you should find some hints.


Best

Kai-Uwe Bux
 
H

Hicham Mouline

Kai-Uwe Bux said:
To some degree: instead of making the class clonable, you can use smart
pointers with deep copy semantics. Search the archives for "clone_ptr" or
"copy_ptr" and you should find some hints.


Best

Kai-Uwe Bux

I found this
http://groups.google.com/group/comp...b857444d?lnk=gst&q=clone_ptr#addaeb3bb857444d
with your code from Dec 2006.

I've also found this
http://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c10407

My case involves a hiearchy of structs. I assume I need to make the base
dtor virtual for the clone_ptr deleter to work.

Actually, how can I use it below?

#include "cloned_ptr.hpp"
#include "Base.hpp"
class Container {
public:
Container(const Base& b)
: base_ptr_( ) //// what to do here?
private:
clone_ptr<Base> base_ptr_;
};

I'd want to do the copy of the most-derived oject referred to by b without
knowing its type.

regards,
 
K

Kai-Uwe Bux

Hicham said:
I found this
http://groups.google.com/group/comp...b857444d?lnk=gst&q=clone_ptr#addaeb3bb857444d
with your code from Dec 2006.

I've also found this
http://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c10407

My case involves a hiearchy of structs. I assume I need to make the base
dtor virtual for the clone_ptr deleter to work.

That depends on the clone_ptr or copy_ptr implementation. The ones inspired
by shared_ptr usually share its feature of _not_ requiring virtual
destructors.
Actually, how can I use it below?

#include "cloned_ptr.hpp"
#include "Base.hpp"
class Container {
public:
Container(const Base& b)
: base_ptr_( ) //// what to do here?
private:
clone_ptr<Base> base_ptr_;
};

I'd want to do the copy of the most-derived oject referred to by b without
knowing its type.

The key is not to lose track of type information. When you new() the object,
the compiler knows, which type it is. At that point, you have to wrap it so
that this type information is not lost. E.g.:

class Container {
public:

template < typename Derived >
Container ( const Derived & other )
: base_ptr_ ( new Derived ( other ) )
{}

private:
clone_ptr<Base> base_ptr_;
};

In short, the use of clone_ptr<> has to be taken into account at the point
of creating objects. You cannot do it as an afterthought: once type
information is lost, it impossible to recover with standard means. (Maybe,
intimate knowledge of a given implementation can allow you to read out
vtable information; but that I would not know about.)


Best

Kai-Uwe Bux
 
H

Hicham Mouline

Kai-Uwe Bux said:
Hicham Mouline wrote:
That depends on the clone_ptr or copy_ptr implementation. The ones
inspired
by shared_ptr usually share its feature of _not_ requiring virtual
destructors.


The key is not to lose track of type information. When you new() the
object,
the compiler knows, which type it is. At that point, you have to wrap it
so
that this type information is not lost. E.g.:

class Container {
public:

template < typename Derived >
Container ( const Derived & other )
: base_ptr_ ( new Derived ( other ) )
{}

private:
clone_ptr<Base> base_ptr_;
};
All right.

Could I also do this:
class Container {
public:

Container ( const clone_ptr<Base>& other )
: base_ptr_ ( other )
{}

private:
clone_ptr<Base> base_ptr_;
};

granted I use clone_ptr when I use other.

Would you going with your implementation from Dec 2006, or would you advise
a different one?

regards,
 
K

Kai-Uwe Bux

Hicham said:
All right.

Could I also do this:
class Container {
public:

Container ( const clone_ptr<Base>& other )
: base_ptr_ ( other )
{}

private:
clone_ptr<Base> base_ptr_;
};

granted I use clone_ptr when I use other.

Looks ok.

Would you going with your implementation from Dec 2006, or would you
advise a different one?

I am probably not the right person to give advice about this: the
implementation was a response to a challenge about clone pointers for
incomplete types. Admittedly, I did not find use for it later :)

Anyway, here is the current version sitting (more or less untested) in my
library (cleaned up a little to remove dependencies on local stuff):

------------------------------------------------
// copy_ptr.cc (C) Kai-Uwe Bux [2007-2010]
// =======================================
/*
The template copy_ptr<T> defines smart pointer with copy
semantics: a pointer assignment copies the pointee. It
supports initialization as

copy_ptr<T> p ( new T ( some args ) );

as well as polymorphic initialization via

copy_ptr<T> p ( new D ( some args ) );

where D is derived from T. In this case, copy construction and
assignment are also supported:

copy_ptr<D> d_ptr ( new D ( some args ) );
copy_ptr<T> t_ptr ( d_ptr );
t_ptr = d_ptr;

No slicing will occur when used according to these idioms.


The template allows for specification of a custom clone method
and a custom deleter. The default cloner does not require T to
have a clone method: it uses copy construction to clone a
pointer.


Note: the type T does not need to be complete.

The interface is closely modelled upon tr1::shared_ptr<>.

// FIXME: [write more]
*/

#include <boost/utility.hpp> // enable_if
#include <tr1/type_traits> // is_base_of
#include <tr1/functional> // function
#include <algorithm> // swap
#include <functional> // less


namespace kubux {

template < typename T >
class copy_ptr {
public:

typedef T value_type;
typedef value_type * pointer;
typedef value_type const * const_pointer;
typedef value_type & reference;
typedef value_type const & const_reference;


// standard deletion and copy strategies
// ======================================

static
void default_delete ( T* p ) {
delete ( p );
}

static
T* default_copy ( T* p ) {
return ( new T (*p) );
}

static
T* default_null ( T* p ) {
return ( 0 );
}

static
void default_do_nothing ( T* p ) {
}

typedef std::tr1::function< void(pointer) > deleter;
typedef std::tr1::function< pointer(pointer) > copier;

private:

pointer the_ptr;
copier the_cln;
deleter the_del;


// support for conversion
// copy_ptr<Derived> to copy_ptr<Base>
// =====================================

template < typename D >
friend class copy_ptr;

/*
The following templates are used to support
initialization of copy_ptr<T> from copy_ptr<D>
where D is derived from T. The idea is that the copier
and deleter object will first upcast the T* member
to a D* and then call the copier/deleter from there.

We use static cast: the code will not compile anyway
unless the assignment D* to T* is valid. In that case,
we may safely assume that upcasting back to D* is ok.
*/

template < typename D >
struct conversion_deleter {

typename copy_ptr<D>::deleter the_deleter;

template < typename P >
conversion_deleter ( P d )
: the_deleter ( d )
{}

void operator() ( pointer ptr ) const {
the_deleter( static_cast<D*>( ptr ) );
}

}; // conversion_deleter

template < typename D >
struct conversion_copier {

typename copy_ptr<D>::copier the_copier;

template < typename P >
conversion_copier ( P c )
: the_copier ( c )
{}

pointer operator() ( pointer ptr ) const {
return( static_cast<pointer>
( the_copier
( static_cast<D*>( ptr ) ) ) );
}

}; // conversion_copier


// pointer casts need to be friends
// ================================

template < typename A, typename B >
friend
copy_ptr<A> static_pointer_cast ( copy_ptr<B> const & );

template < typename A, typename B >
friend
copy_ptr<A> dynamic_pointer_cast ( copy_ptr<B> const & );

template < typename A, typename B >
friend
copy_ptr<A> const_pointer_cast ( copy_ptr<B> const & );


public:

// swap
// ====

void swap ( copy_ptr & other ) {
std::swap( the_cln, other.the_cln );
std::swap( the_del, other.the_del );
std::swap( the_ptr, other.the_ptr );
}


// default constructor [0 pointer]
// ===============================

copy_ptr ( void )
: the_ptr ( 0 )
, the_cln ( &default_null )
, the_del ( &default_do_nothing )
{}


// construction from pointer
// =========================

explicit
copy_ptr ( pointer ptr )
: the_ptr ( ptr )
, the_cln ( ptr == 0 ? &default_null : &default_copy )
, the_del ( ptr == 0 ? &default_do_nothing : &default_delete )
{}

template < typename Cloner >
copy_ptr ( pointer ptr, Cloner c )
: the_ptr ( ptr )
, the_cln ( c )
, the_del ( ptr == 0 ? &default_do_nothing : &default_delete )
{}

template < typename Cloner, typename Deleter >
copy_ptr ( pointer ptr, Cloner c, Deleter d )
: the_ptr ( ptr )
, the_cln ( c )
, the_del ( d )
{}


// copy constructor
// ================

copy_ptr ( copy_ptr const & other )
: the_ptr ( other.the_cln( other.the_ptr ) )
, the_cln ( other.the_cln )
, the_del ( other.the_del )
{}


// constructor variants from derived types
// =======================================

template < typename D >
explicit
copy_ptr ( D* ptr )
: the_ptr ( ptr )
, the_cln ( conversion_copier<D>
( ptr == 0 ?
&copy_ptr<D>::default_null :
&copy_ptr<D>::default_copy ) )
, the_del ( conversion_deleter<D>
( ptr == 0 ?
&copy_ptr<D>::default_do_nothing :
&copy_ptr<D>::default_delete ) )
{}


template < typename D, typename Cloner >
explicit
copy_ptr ( D* ptr, Cloner c )
: the_ptr ( ptr )
, the_cln ( conversion_copier<D>( c ) )
, the_del ( conversion_deleter<D>
( ptr == 0 ?
&copy_ptr<D>::default_do_nothing :
&copy_ptr<D>::default_delete ) )
{}

template < typename D, typename Cloner, typename Deleter >
explicit
copy_ptr ( D* ptr, Cloner c, Deleter d )
: the_ptr ( ptr )
, the_cln ( conversion_copier<D>( c ) )
, the_del ( conversion_deleter<D>( d ) )
{}


/*
We use enable_if to prevent certain conversion
so that comparisions

copy_ptr<Base> == copy_ptr<Derived>

do not have ambigous overloads.
*/
template < typename D >
copy_ptr ( copy_ptr<D> const & other,
typename boost::enable_if<
std::tr1::is_base_of<value_type,D>,
void* >::type dummy = 0 )
: the_ptr ( other.the_cln( other.the_ptr ) )
, the_cln ( conversion_copier<D>( other.the_cln ) )
, the_del ( conversion_deleter<D>( other.the_del ) )
{}


// destructor
// ==========

~copy_ptr ( void ) {
the_del( the_ptr );
}


// copy-swap assignment operators
// ==============================

copy_ptr & operator= ( copy_ptr const & other ) {
copy_ptr dummy ( other );
swap( dummy );
return ( *this );
}

template < typename D >
copy_ptr & operator= ( copy_ptr<D> const & other ) {
copy_ptr dummy ( other );
swap( dummy );
return ( *this );
}

// dereferencing operators
// =======================

pointer operator-> ( void ) const {
return ( the_ptr );
}

reference operator* ( void ) const {
return ( *the_ptr );
}


// observers
// =========

operator bool ( void ) const {
return ( get() != 0 );
}

pointer get ( void ) {
return ( the_ptr );
}

const_pointer get ( void ) const {
return ( the_ptr );
}

template < typename Cloner >
friend
Cloner * get_copier ( copy_ptr & c_ptr ) {
return ( c_ptr.the_del.template target<Cloner>() );
}

template < typename Cloner >
friend
Cloner const * get_copier ( copy_ptr const & c_ptr ) {
return ( c_ptr.the_del.template target<Cloner>() );
}

template < typename Deleter >
friend
Deleter * get_deleter ( copy_ptr & c_ptr ) {
return ( c_ptr.the_del.template target<Deleter>() );
}

template < typename Deleter >
friend
Deleter const * get_deleter ( copy_ptr const & c_ptr ) {
return ( c_ptr.the_del.template target<Deleter>() );
}

// modifiers
// =========

void reset ( T* ptr ) {
shared_ptr ( ptr ).swap( *this );
}

template < typename D >
void reset ( D* ptr ) {
shared_ptr( ptr ).swap( *this );
}

template < typename D, typename Cloner >
void reset ( D* ptr, Cloner c ) {
shared_ptr( ptr, c ).swap( *this );
}

template < typename D, typename Cloner, typename Deleter >
void reset ( D* ptr, Cloner c, Deleter d ) {
shared_ptr( ptr, c, d ).swap( *this );
}


// null pointer constant
// =====================

static
copy_ptr const & nil ( void ) {
static copy_ptr const nil_ptr;
return nil_ptr;
}

}; // copy_ptr<>


// specialized algorithms
template < typename T >
void swap ( copy_ptr<T> & a, copy_ptr<T> & b ) {
a.swap( b );
}


// comparison operators
// ====================

template < typename A, typename B >
bool operator== ( copy_ptr<A> const & lhs,
copy_ptr<B> const & rhs ) {
return ( lhs.get() == rhs.get() );
}

template < typename A, typename B >
bool operator!= ( copy_ptr<A> const & lhs,
copy_ptr<B> const & rhs ) {
return ( lhs.get() != rhs.get() );
}

template < typename A, typename B >
bool operator< ( copy_ptr<A> const & lhs,
copy_ptr<B> const & rhs ) {
return ( std::less<void*>()
( static_cast<void*>(lhs.get()),
static_cast<void*>(rhs.get()) ) );
}


// pointer casts (friends)
// =======================

template < typename A, typename B >
copy_ptr<A> static_pointer_cast ( copy_ptr<B> const & b_ptr ) {
if ( b_ptr ) {
A* a_ptr = static_cast<A*>( b_ptr.the_cln( b_ptr.the_ptr ) );
return
copy_ptr<A>
( a_ptr,
typename copy_ptr<A>::template
conversion_copier<B>( b_ptr.the_cln ),
typename copy_ptr<A>::template
conversion_deleter<B>( b_ptr.the_del ) );
} else {
return ( copy_ptr<A>() );
}
}

template < typename A, typename B >
copy_ptr<A> dynamic_pointer_cast ( copy_ptr<B> const & b_ptr ) {
B* b_copy ( b_ptr.the_cln( b_ptr.the_ptr ) );
A* a_ptr = dynamic_cast<A*>( b_copy.get() );
if ( a_ptr == 0 ) {
b_ptr.the_del( b_copy );
return copy_ptr<A>();
} else {
return
copy_ptr<A>
( a_ptr,
typename copy_ptr<A>::template
conversion_copier<B>( b_ptr.the_cln ),
typename copy_ptr<A>::template
conversion_deleter<B>( b_ptr.the_del ) );
}
}

template < typename A, typename B >
copy_ptr<A> const_pointer_cast ( copy_ptr<B> const & b_ptr ) {
if ( b_ptr ) {
A* a_ptr = const_cast<A*>( b_ptr.the_cln( b_ptr.the_ptr ) );
return
copy_ptr<A>
( a_ptr,
typename copy_ptr<A>::template
conversion_copier<B>( b_ptr.the_cln ),
typename copy_ptr<A>::template
conversion_deleter<B>( b_ptr.the_del ) );
} else {
return ( copy_ptr<A>() );
}
}

} // namespace kubux

// end of file
------------------------------------------

If you decide to try it, I would be very interesting to hear about wether it
works well for you or what the problems are.


Best

Kai-Uwe Bux
 

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,982
Messages
2,570,186
Members
46,744
Latest member
CortneyMcK

Latest Threads

Top