Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Virtual constructor?

46 views
Skip to first unread message

Azumanga

unread,
May 31, 2006, 3:56:38 PM5/31/06
to
Imagine I have the following code:

struct A { .. };
struct B : public A { .. };
struct C : public A { .. };

// Copy the given object, return the copy
A* copy_object(A* obj)
{ ?? }

Does anyone have any good ideas on how to implement this function? At
the moment, I have two methods I can think of (sorry if these don't
actually compile, I have not got a compiler to hand here. The basic
ideas are I believe good however).

A* copy_object(A* obj)
{
if(B* b_obj = dynamic_cast<B*>(obj))
{ return new B(*b_obj); }
...
}

or:

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

struct B : public A
{ virtual B* clone() { return new B(*this);} ... };

...

Both of these seem error prone, and require adding a new function for
everything in my heirachy. Is there a better way?

Chris


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Ulrich Eckhardt

unread,
May 31, 2006, 5:01:42 PM5/31/06
to
I'd like to address your topic first: there can be no virtual constructor
because the virtual always requires an existing object. That is not your
problem though...

Azumanga wrote:
> Imagine I have the following code:
>
> struct A { .. };
> struct B : public A { .. };
> struct C : public A { .. };
>
> // Copy the given object, return the copy
> A* copy_object(A* obj)
> { ?? }
>
> Does anyone have any good ideas on how to implement this function?

[idea 1: using dynamic_cast to detect the type and create a copy of it]


[idea 2:]


> struct A
> { virtual A* clone() { return new A(*this);} ... };
>
> struct B : public A
> { virtual B* clone() { return new B(*this);} ... };

This is the way it is typically done, with one minor mod: It creates a
copy, so the original should not need to be modified (const correctness).

This thing is then called a 'virtual copy constructor' or sometimes
also 'named copy constructor'.

> Both of these seem error prone, and require adding a new function for
> everything in my heirachy. Is there a better way?

Not that I'd know. I'd like to point out that both methods have their
advantages. The first is usually considered a greater cludge than the
second and "less OO", but it offers all related code in one place and is
nonintrusive. It could have been improved by not using dynamic_cast but
using typeid() and comparing for equality - this has the great advantage
that when class D, derived from B, is added it doesn't trigger the code
that copies a B.

Uli

Kai-Uwe Bux

unread,
Jun 1, 2006, 7:17:47 AM6/1/06
to
Azumanga wrote:

> Imagine I have the following code:
>
> struct A { .. };
> struct B : public A { .. };
> struct C : public A { .. };
>
> // Copy the given object, return the copy
> A* copy_object(A* obj)
> { ?? }
>
> Does anyone have any good ideas on how to implement this function? At
> the moment, I have two methods I can think of (sorry if these don't
> actually compile, I have not got a compiler to hand here. The basic
> ideas are I believe good however).
>
> A* copy_object(A* obj)
> {
> if(B* b_obj = dynamic_cast<B*>(obj))
> { return new B(*b_obj); }
> ...
> }
>
> or:
>
> struct A
> { virtual A* clone() { return new A(*this);} ... };
>
> struct B : public A
> { virtual B* clone() { return new B(*this);} ... };
>
> ...
>
> Both of these seem error prone, and require adding a new function for
> everything in my heirachy. Is there a better way?

The following is a simple implementation of a smart pointer with copy
semantics. I post it only to illustrate the principle. A google search for
copy_ptr or clone_ptr should take you to more refined versions. If I recall
correctly, Axter has some on www.axter.com:

// copy_ptr.cc (C) Kai-Uwe Bux [2005]
// ==================================

/*
| - 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 () );

}


Best

Kai-Uwe Bux

Gene Bushuyev

unread,
Jun 1, 2006, 4:57:13 PM6/1/06
to
"Azumanga" <4zum...@gmail.com> wrote in message
news:1149031273....@v35g2000cwv.googlegroups.com...

> Imagine I have the following code:
>
> struct A { .. };
> struct B : public A { .. };
> struct C : public A { .. };
[...]

> A* copy_object(A* obj)
> {
> if(B* b_obj = dynamic_cast<B*>(obj))
> { return new B(*b_obj); }

This code is an absolute anathema to object-oriented programming. Each code that
relies on switching on types is not only error-prone, it breaks the foundational
open-close design principle. Your function must know about every possible
derivative of A, changing every time whenever the classes are changed or new
classes added.

>
> or:
>
> struct A
> { virtual A* clone() { return new A(*this);} ... };
>
> struct B : public A
> { virtual B* clone() { return new B(*this);} ... };

This is a typical way of cloning objects (except it should be const,) which is
sometimes called "virtual constructor." There is nothing error-prone about it,
the only inconvenience is typing virtually the same code in every class.

--
Gene Bushuyev (www.gbresearch.com)
----------------------------------------------------------------
To see what is in front of one's nose needs a constant struggle ~ George Orwell

Seungbeom Kim

unread,
Jun 2, 2006, 6:40:16 PM6/2/06
to
Ulrich Eckhardt wrote:
> I'd like to address your topic first: there can be no virtual constructor
> because the virtual always requires an existing object. That is not your
> problem though...

Just below you mention a 'virtual copy constructor', but here you say
there can be no virtual constructor?

If 'virtual' is used in the strict sense defined by the language, there
can be no virtual constructors of any kind, whether default constructors
or copy constructors. Otherwise, if it's used to mean an idiom, you
*can* implement a virtual default constructor just as a virtual copy
constructor: something that depends on an existing object and creates a
new one of the same type.

http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.8

> [idea 2:]
>> struct A
>> { virtual A* clone() { return new A(*this);} ... };
>>
>> struct B : public A
>> { virtual B* clone() { return new B(*this);} ... };
>
> This is the way it is typically done, with one minor mod: It creates a
> copy, so the original should not need to be modified (const correctness).
>
> This thing is then called a 'virtual copy constructor' or sometimes
> also 'named copy constructor'.

--
Seungbeom Kim

Rob

unread,
Jun 3, 2006, 10:09:04 AM6/3/06
to
Seungbeom Kim wrote:
>
> Just below you mention a 'virtual copy constructor', but here you say
> there can be no virtual constructor?
>

A constructor cannot be virtual.

However, let's say we have;

class X
{
public:
virtual X *Clone() const = 0;
};

class Y : public X
{
public:
virtual Y *Clone() const {return new Y(*this);};
}

This technique/idiom is often referred to as the "virtual copy
constructor". A better name needed to avoid confusion like yours,
but that's life: we humans have to cope with ambiguity.

ThosRTanner

unread,
Jun 3, 2006, 10:01:57 AM6/3/06
to

Gene Bushuyev wrote:
> "Azumanga" <4zum...@gmail.com> wrote in message
> news:1149031273....@v35g2000cwv.googlegroups.com...
> > Imagine I have the following code:
> >
> > struct A { .. };
> > struct B : public A { .. };
> > struct C : public A { .. };
> [...]
> > A* copy_object(A* obj)
> > {
> > if(B* b_obj = dynamic_cast<B*>(obj))
> > { return new B(*b_obj); }
>
> This code is an absolute anathema to object-oriented programming. Each code that
> relies on switching on types is not only error-prone, it breaks the foundational
> open-close design principle. Your function must know about every possible
> derivative of A, changing every time whenever the classes are changed or new
> classes added.
>
> >
> > or:
> >
> > struct A
> > { virtual A* clone() { return new A(*this);} ... };
> >
> > struct B : public A
> > { virtual B* clone() { return new B(*this);} ... };
>
> This is a typical way of cloning objects (except it should be const,) which is
> sometimes called "virtual constructor." There is nothing error-prone about it,
> the only inconvenience is typing virtually the same code in every class.
>

Isn't this one of those places where you use the Curiously Recurring
Template pattern

template <class C> cloneable { virtual C* clone() return new C(*this) }

and then do
struct B : public A, cloneable<B> { .... }

I haven't used the pattern that much but I think it works something
like that.

Gene Bushuyev

unread,
Jun 4, 2006, 9:11:01 AM6/4/06
to
"ThosRTanner" <ttan...@bloomberg.net> wrote in message
news:1149253982.0...@i40g2000cwc.googlegroups.com...
[...]

>> > struct A
>> > { virtual A* clone() { return new A(*this);} ... };
>> >
>> > struct B : public A
>> > { virtual B* clone() { return new B(*this);} ... };
>>
>> This is a typical way of cloning objects (except it should be const,) which
>> is
>> sometimes called "virtual constructor." There is nothing error-prone about
>> it,
>> the only inconvenience is typing virtually the same code in every class.
>>
>
> Isn't this one of those places where you use the Curiously Recurring
> Template pattern
>
> template <class C> cloneable { virtual C* clone() return new C(*this) }
>
> and then do
> struct B : public A, cloneable<B> { .... }
>
> I haven't used the pattern that much but I think it works something
> like that.

The problem with using CRTP is that it introduces ambiguity due to multiple
inheritance, rather than overriding the virtual clone() function. Here is an
example,

template <class C>
class cloneable
{
public:
virtual C* clone() const { return new C(*this); }
};

class A : public cloneable<A> { public: A(const A&); A();};

class B : public A, public cloneable<B>
{ public: B(const B&); B();};

int main()
{
B* b = new B;
B* c = b->clone();
}

compiler cannot resolve the ambiguity between cloneable<A>::clone() and
cloneable<B>::clone().

--
Gene Bushuyev (www.gbresearch.com)
----------------------------------------------------------------
To see what is in front of one's nose needs a constant struggle ~ George Orwell

Seungbeom Kim

unread,
Jun 4, 2006, 9:17:03 AM6/4/06
to
Rob wrote:
>
> A constructor cannot be virtual.

As I said.

> However, let's say we have;
>
> class X
> {
> public:
> virtual X *Clone() const = 0;
> };
>
> class Y : public X
> {
> public:
> virtual Y *Clone() const {return new Y(*this);};
> }
>
> This technique/idiom is often referred to as the "virtual copy
> constructor".

As I said, and even mentioned an FAQ item describing exactly that.

> A better name needed to avoid confusion like yours,
> but that's life: we humans have to cope with ambiguity.

I didn't have any confusion. I said:

"If 'virtual' is used in the strict sense defined by the language, there
can be no virtual constructors of any kind, whether default constructors
or copy constructors. Otherwise, if it's used to mean an idiom, you
*can* implement a virtual default constructor just as a virtual copy
constructor: something that depends on an existing object and creates a
new one of the same type."

If there's anything unclear in this explanation, let me know
(explicitly). Otherwise, did you actually read what you replied to?

--
Seungbeom Kim

god...@gmail.com

unread,
Jun 5, 2006, 12:50:57 PM6/5/06
to
Gene Bushuyev wrote:
> The problem with using CRTP is that it introduces ambiguity due to multiple
> inheritance, rather than overriding the virtual clone() function. Here is an
> example,
>
> template <class C>
> class cloneable
> {
> public:
> virtual C* clone() const { return new C(*this); }
> };
>
> class A : public cloneable<A> { public: A(const A&); A();};
>
> class B : public A, public cloneable<B>
> { public: B(const B&); B();};
>
> int main()
> {
> B* b = new B;
> B* c = b->clone();
> }
>
> compiler cannot resolve the ambiguity between cloneable<A>::clone() and
> cloneable<B>::clone().
>
> --
> Gene Bushuyev (www.gbresearch.com)
> ----------------------------------------------------------------
> To see what is in front of one's nose needs a constant struggle ~ George Orwell

I don't know if this would work, and somehow I got different error
messages
from compiler to compiler.


template <class T>
class cloner{
protected:
T* clone() const{ return new T(*static_cast<T const*>(this)); }
cloner(){}
};

struct null_type{};

template <class T, class Parent = null_type>
struct cloneable: public Parent, private cloner<T>{
virtual T* clone() const{ return cloner<T>::clone(); }
// ^^^ note here

};

class Base: public cloneable<Base>{};
class Derived: public cloneable<Derived, Base>{};

==

Comeau C/C++ 4.3.3:

error: return type is not identical to nor covariant
with return type "Base *" of overridden virtual function

If I change T* to Parent*, it compiles.

==

GCC 3.4.5:

error: invalid covariant return type for `T* cloneable<T,
Parent>::clone() const [with T = Derived, Parent = Base]'
error: `cloner<Base>' is an inaccessible base of `Base'

If I change private inheritance to protected inheritance, plus
changing T* to Parent*, it compiles.

==

VC++ 7.1:

error C2555: 'cloneable<T,Parent>::clone': overriding virtual
function return type differs and is not covariant from
'cloneable<T>::clone'

If I change T* to Parent*, it yells another error:
error C2244: 'cloneable<T,Parent>::clone' : unable to match function
definition to an existing declaration

The only way to compile under VC++ 7.1 is change T* to null_type* or
void*, etc.

==

Could anyone elaborate for me?

kanze

unread,
Jun 6, 2006, 9:40:15 AM6/6/06
to
god...@gmail.com wrote:
> Gene Bushuyev wrote:

> > The problem with using CRTP is that it introduces ambiguity
> > due to multiple inheritance, rather than overriding the
> > virtual clone() function.

That's only a problem if you are doing it incorrectly.
(ThosRTanner admitted that "I haven't used the pattern that much
but I think it works something like that.")

I'm not sure of the exact limits of the term CRTP, but what is
wanted here is a class template which is inserted between the
most derived class and its immediate base, not along side of it.

> > Here is an example,

> > template <class C>
> > class cloneable
> > {
> > public:
> > virtual C* clone() const { return new C(*this); }
> > };

> > class A : public cloneable<A> { public: A(const A&); A();};

> > class B : public A, public cloneable<B>
> > { public: B(const B&); B();};

> > int main()
> > {
> > B* b = new B;
> > B* c = b->clone();
> > }

> > compiler cannot resolve the ambiguity between
> > cloneable<A>::clone() and cloneable<B>::clone().

There are several different problems here. First, of course, is
that the base of the hierarchy wouldn't normally be a template,
and wouldn't derive from the Clonable template. In fact, no
abstract class can derive from the Clonable template; since
clone is a virtual function, it is considered "used" anytime an
instance of the class is constructed. And because it is used,
it will be instantiated, and the instantiation of the clone
function will fail because it attempts to instantiate an object
with a type of an abstract class.

> I don't know if this would work, and somehow I got different
> error messages from compiler to compiler.

> template <class T>
> class cloner{
> protected:
> T* clone() const{ return new T(*static_cast<T const*>(this)); }
> cloner(){}
> };

> struct null_type{};

> template <class T, class Parent = null_type>
> struct cloneable: public Parent, private cloner<T>{
> virtual T* clone() const{ return cloner<T>::clone(); }
> // ^^^ note here
>
> };

I can't figure out what you're trying to do here.

I've indicated the standard idiom in another posting. It can
easily be modified to support both programming by contract and
(pseudo-)co-variant returns, something like:

template< typename Derived, typename Base >
class Clonable : public Base
{
public:
Derived* clone() const
{
return new Derived(
*dynamic_cast< Derived const* >( this ) ) ;
}

private:
virtual Base* doClone() const
{
return clone() ;
}

} ;

template< typename Base >
class AbstractClonable
{
public:
AbstractClonable* clone() const
{
Base* result = doClone() ;
assert( typeid( result ) == typeid( *this ) ) ;
return result ;
}

private:
virtual Base* doClone() const = 0 ;
} ;

If you really want to use the same name for both classes, you'll
have to define some dummy type, make it the default parameter
for Base in Clonable, and then provide a partial specialization
for the case where it is the parameter for Base, which does what
AbstractClonable does above.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

kanze

unread,
Jun 6, 2006, 9:39:50 AM6/6/06
to

> > > or:

One can, although it would more likely be something like:

template< typename Derived, typename Base >

class Clonable : public base
{
virtual Base* clone() const


{
return new Derived( *dynamic_cast< Derived const* >( this )
) ;
}

} ;

class B : public Clonable< B, A > ...

Otherwise, the function clone in the template won't override the
function clone in the base class.

Personally, I'm not sure its worth it for just the one function;
if several functions are involved, or the function is more
complex, however, it is certainly worth considering.

Another frequent variation is to implement verifications in the
base class, i.e.:

class Base
{
public:
/* NOT virtual */ Base* clone() const
{
Base* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}

private:
virtual Base* doClone() const = 0 ;
} ;

This way, if anyone forgets to override doClone, you find out
about it immediately. (If doClone is pure virtual, an
immediately derived class cannot forget. But if the hierarchy
has a certain depth, it could conceivably happen.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Ulrich Eckhardt

unread,
Jun 6, 2006, 6:07:32 PM6/6/06
to
Seungbeom Kim wrote:
> Ulrich Eckhardt wrote:
>> I'd like to address your topic first: there can be no virtual
>> constructor because the virtual always requires an existing object.
>> That is not your problem though...
>
> Just below you mention a 'virtual copy constructor', but here you say
> there can be no virtual constructor?
>
> If 'virtual' is used in the strict sense defined by the language, there
> can be no virtual constructors of any kind, whether default constructors
> or copy constructors.

Right.

> Otherwise, if it's used to mean an idiom, you *can* implement a virtual
> default constructor just as a virtual copy constructor: something that
> depends on an existing object and creates a new one of the same type.

Right.

I admit that my statements were not very precise. Indeed, in the same way
that you can implement a 'virtual' copy constructor, where the created
object's type depends on another object's type, you can create a 'virtual'
default constructor.

One big difference between a constructor and a copy constructor is that in
one case an object is created and in the other, an object is created and
made a copy of an existing object. The second case thus lends itself much
better to making it polymorphic than the first, because you always have a
second object involved. Also, the requirement for a 'virtual copy
constructor' is much more common than the requirement for a 'virtual
constructor'.

I fully agree that technically both are possible, though.

Uli

Vladimir Marko

unread,
Jun 7, 2006, 6:19:38 PM6/7/06
to
kanze wrote:
[...]

> I've indicated the standard idiom in another posting. It can
> easily be modified to support both programming by contract and
> (pseudo-)co-variant returns, something like:
>
> template< typename Derived, typename Base >
> class Clonable : public Base
> {
> public:
> Derived* clone() const
> {
> return new Derived(
> *dynamic_cast< Derived const* >( this ) ) ;
> }
>
> private:
> virtual Base* doClone() const
> {
> return clone() ;
> }
>
> } ;

This doesn't make sense to me. Who calls the _private_ function
doClone? Perhaps you meant something like

template <class Derived,class Base>


class Clonable : public Base {
public:
Derived* clone() const{

return static_cast<Derived*>(this->doClone());
}
private:
virtual Clonable* doClone() const{
assert(typeid(*this)==typeid(Derived));
return new Derived(static_cast<const Derived&>(*this));
}
};

As with the other post, static_cast is sufficient even though it's
not that easy to see this time.

Cheers

Vladimir Marko

Vladimir Marko

unread,
Jun 7, 2006, 6:20:03 PM6/7/06
to
kanze wrote:
> ThosRTanner wrote:
[...]

> > Isn't this one of those places where you use the Curiously
> > Recurring Template pattern
>
> > template <class C> cloneable { virtual C* clone() return new C(*this) }
>
> > and then do
> > struct B : public A, cloneable<B> { .... }
>
> One can, although it would more likely be something like:
>
> template< typename Derived, typename Base >
> class Clonable : public base
> {
> virtual Base* clone() const
> {
> return new Derived( *dynamic_cast< Derived const* >( this )
> ) ;
> }
> } ;
>
> class B : public Clonable< B, A > ...
>
> Otherwise, the function clone in the template won't override the
> function clone in the base class.

Why dynamic_cast when static_cast is sufficient? You can't use this
for virtual inheritance anyway:

class B{ ... };
class B1: virtual public Clonable<B1,B> { ... };
class B2: virtual public Clonable<B2,B> { ... };
class D: public Clonable2<D,B1, B2> { ... };
// D contains _two_ subobjects of class B

You'd need to add some traits to the template parameters for Clonable
to indicate the type of inheritance (public/private/protected,
normal/virtual). And, of course, to ClonableX for multiple inheritance.

The other problem is the return type of the clone function. Ideally it
should be Derived*, but this is not possible. There's been a thread in
clc++m in late 2004 where I suggested the following:

// WARNING: THIS DOES NOT WORK
template <class Derived,class Base>
struct Clonable: Base {
virtual Derived* clone() const{
return new Derived(static_cast<const Derived&>(*this));
}
};

struct B { virtual B* clone() const =0; virtual ~B(); };
struct D: Clonable<D,B> { };

The idea is that the Clonable<D,B>::clone's result type would be D*
which is covariant with B*. A few months later someone asked about
this again and I discovered that it can't work. The problem is that
during the _instantiation_ of the _declaration_ of Clonable<D,B>::clone
D is still an incomplete type and the base-derived relation between D
and B doesn't exist yet.

In the end it seems that writing the clone function manually is the
best option.

Cheers

Vladimir

Seungbeom Kim

unread,
Jun 8, 2006, 6:49:21 AM6/8/06
to
Ulrich Eckhardt wrote:

> Seungbeom Kim wrote:
>> Otherwise, if it's used to mean an idiom, you *can* implement a virtual
>> default constructor just as a virtual copy constructor: something that
>> depends on an existing object and creates a new one of the same type.
>
> Right.
>
> I admit that my statements were not very precise. Indeed, in the same way
> that you can implement a 'virtual' copy constructor, where the created
> object's type depends on another object's type, you can create a 'virtual'
> default constructor.
>
> One big difference between a constructor and a copy constructor is that in
> one case an object is created and in the other, an object is created and
> made a copy of an existing object. The second case thus lends itself much
> better to making it polymorphic than the first, because you always have a
> second object involved. Also, the requirement for a 'virtual copy
> constructor' is much more common than the requirement for a 'virtual
> constructor'.
>
> I fully agree that technically both are possible, though.

If it had been just for a purely technical possibility,
I wouldn't have bothered to post the previous article. ;-)

Let me share with you an experience of mine: I have actually implemented
and used 'virtual' constructors that did not clone existing objects but
created new ones. (Strictly they weren't 'default' constructors, though,
but they didn't copy anything from existing objects (of the same type).)

I had a class hierarchy of messages, which are serialized from/into a
byte stream. Each message class had a distinct type ID and a virtual
constructor that took a byte stream as its argument.

struct message
{
virtual ~message() { }
virtual uint16_t message_type() const = 0;
virtual message* create(istream&) const = 0;
};

struct some_message : message
{
virtual uint16_t message_type() const { return 0xCAFEu; }
virtual message* create(istream& s) const
{ return new some_message(s); }
explicit some_message(istream& s) /* : ... */ { /* ... */ }
// ...
};

// lots of other messages

And a map from type IDs into default constructed messages were built.

std::map<uint16_t, const message*> msg_map;
void register(const message* m) { msg_map[m->message_type()] = m; }

register(new some_message);
// ...

Later when a message is to be reconstructed from a byte stream, its type
ID was read first, and the map was looked up for the type ID, and the
virtual default constructor (called "create") was called:

istream& is;
uint16_t msg_type;
is >> msg_type;
const msg_info& i = msg_map.find(msg_type)->second;
// error checking not shown here for brevity
std::auto_ptr<message> msg(i.prototype->create(is));

The information flow is like the following:

("prototype") (real message)
uint16_t -----------> message* --------------> message*
map::find virtual ctor

I hope this example will be helpful.

--
Seungbeom Kim

kanze

unread,
Jun 8, 2006, 6:52:09 PM6/8/06
to
Vladimir Marko wrote:
> kanze wrote:
> > ThosRTanner wrote:
> [...]
> > > Isn't this one of those places where you use the Curiously
> > > Recurring Template pattern

> > > template <class C> cloneable { virtual C* clone() return new C(*this) }

> > > and then do
> > > struct B : public A, cloneable<B> { .... }

> > One can, although it would more likely be something like:

> > template< typename Derived, typename Base >

> > class Clonable : public Base


> > {
> > virtual Base* clone() const
> > {
> > return new Derived( *dynamic_cast< Derived const* >( this )
> > ) ;
> > }
> > } ;

> > class B : public Clonable< B, A > ...

> > Otherwise, the function clone in the template won't override
> > the function clone in the base class.

> Why dynamic_cast when static_cast is sufficient?

Because I'm not perfect, and neither are my users. Some sort of
verification is necessary, for cases where someone accidentally
writes:

class D1 : public Clonable< D2, B > ...

(This sort of thing happens to me a lot; very often, the derived
classes contain a significant amount of boilerplate code --
forwarding constructors, and so on. So I start with a
copy/paste of an existing derived class, and forget to edit the
template parameters.)

Starting from scratch, today, I think I'd look into some sort of
static check, Boost's static assert, or something along those
lines -- it's not difficult to verify statically in Clonable
that Derived derives from Clonable< Derived >.

> You can't use this for virtual inheritance anyway:

> class B{ ... };
> class B1: virtual public Clonable<B1,B> { ... };
> class B2: virtual public Clonable<B2,B> { ... };
> class D: public Clonable2<D,B1, B2> { ... };
> // D contains _two_ subobjects of class B

Obviously, in a production version, you'd inherit virtually in
the template clonable, to avoid this problem. Or you'd have two
versions of Clonable, one with direcct virtual inheritance, and
another with direct non-virtual inheritance, and virtual
inheritance of AbstractClonable.

> You'd need to add some traits to the template parameters for
> Clonable to indicate the type of inheritance
> (public/private/protected, normal/virtual). And, of course, to
> ClonableX for multiple inheritance.

For example. A lot depends on your application; writing a
single, totally generic Clonable which covers all cases is
probably possible, but certainly a lot of work, probably most of
which won't be needed.

> The other problem is the return type of the clone function.
> Ideally it should be Derived*, but this is not possible.

Normally, I disagree; I've never had a case where just returning
Base* everywhere caused any problems. But if you do want for
clone in the derived clonable to return Derived*, then the
Barton and Nackman trick of using a non-virtual function (here
clone) in the base class, and hiding it in all of the derived
classes, does. Of course, it doesn't extend very well to N
layers of inheritance, but then, what does?

> There's been a thread in clc++m in late 2004 where I suggested
> the following:

> // WARNING: THIS DOES NOT WORK
> template <class Derived,class Base>
> struct Clonable: Base {
> virtual Derived* clone() const{
> return new Derived(static_cast<const Derived&>(*this));
> }
> };

> struct B { virtual B* clone() const =0; virtual ~B(); };
> struct D: Clonable<D,B> { };

> The idea is that the Clonable<D,B>::clone's result type would
> be D* which is covariant with B*. A few months later someone
> asked about this again and I discovered that it can't work.
> The problem is that during the _instantiation_ of the
> _declaration_ of Clonable<D,B>::clone D is still an incomplete
> type and the base-derived relation between D and B doesn't
> exist yet.

Interesting. A subtle problem; I'll admit that it wouldn't have
occured to me.

> In the end it seems that writing the clone function manually
> is the best option.

Well, it seems simple enough to me to not be worth the bother of
doing it otherwise. But others seem to disagree.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

kanze

unread,
Jun 8, 2006, 6:58:31 PM6/8/06
to
Vladimir Marko wrote:
> kanze wrote:
> [...]
> > I've indicated the standard idiom in another posting. It can
> > easily be modified to support both programming by contract and
> > (pseudo-)co-variant returns, something like:

> > template< typename Derived, typename Base >
> > class Clonable : public Base
> > {
> > public:
> > Derived* clone() const
> > {
> > return new Derived(
> > *dynamic_cast< Derived const* >( this ) ) ;
> > }

> > private:
> > virtual Base* doClone() const
> > {
> > return clone() ;
> > }
> >
> > } ;

> This doesn't make sense to me. Who calls the _private_
> function doClone?

The version of clone in the abstract base class, which
immediately followed.

> Perhaps you meant something like

> template <class Derived,class Base>
> class Clonable : public Base {
> public:
> Derived* clone() const{
> return static_cast<Derived*>(this->doClone());
> }
> private:
> virtual Clonable* doClone() const{
> assert(typeid(*this)==typeid(Derived));
> return new Derived(static_cast<const Derived&>(*this));
> }
> };

Not at all.

> As with the other post, static_cast is sufficient even though
> it's not that easy to see this time.

But you don't need to cast at all in my version. And if you
know the type, you don't even get a virtual function call.
(Irrelevant, really, in this case, but I've used this trick in
other cases where it did have significant performance
implications. In fact, I got the trick from Barton and Nackman,
where they use it mainly because of the performance implications
of a virtual function call in something like
Array<T>::operator[]() -- the implementation in the abstract
base class calls a virtual function, and in each of the derived
classes, the virtual function just calls an inline non-virtual
function with the same name as the non-virtual function in the
base class. If performance is a problem, you arrange to know
the type, and call the function in the derived class.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Wu Yongwei

unread,
Jun 9, 2006, 7:42:10 PM6/9/06
to
kanze wrote:
> template< typename Derived, typename Base >
> class Clonable : public Base
> {
> public:
> Derived* clone() const
> {
> return new Derived(
> *dynamic_cast< Derived const* >( this ) ) ;
> }
>
> private:
> virtual Base* doClone() const
> {
> return clone() ;
> }
>
> } ;
>
> template< typename Base >
> class AbstractClonable
> {
> public:
> AbstractClonable* clone() const
> {
> Base* result = doClone() ;
> assert( typeid( result ) == typeid( *this ) ) ;
> return result ;
> }
>
> private:
> virtual Base* doClone() const = 0 ;
> } ;

Hi James,

I actually find your code quite useful. However, I do find an issue,
when the class hierarchy is as follows:

class A : public public AbstractClonable<A> {};
class B : public Clonable<B, A> {};
class C : public Clonable<C, A> {}'

B* p = new C();

Then p->clone() will really return a B object instead of C.

The solutions I find so far are:

1) Declare only leaf-node objects can be instantiated and let B inherit
from A instead of Clonable<B, A>;
2) Change Clonable::clone() to this effect:

Derived* clone() const
{
if (typeid(Derived) == typeid(*this))
return new Derived(*static_cast<const Derived*>(this));
else
return static_cast<Derived*>(doClone());
}

Any comments? Any better solutions? Anyone knows about the overhead of
typeid?

Best regards,

Yongwei

James Kanze

unread,
Jun 11, 2006, 5:32:24 PM6/11/06
to
Wu Yongwei wrote:
> kanze wrote:
>> template< typename Derived, typename Base >
>> class Clonable : public Base
>> {
>> public:
>> Derived* clone() const
>> {
>> return new Derived(
>> *dynamic_cast< Derived const* >( this ) ) ;
>> }

>> private:
>> virtual Base* doClone() const
>> {
>> return clone() ;
>> }
>> } ;

>> template< typename Base >
>> class AbstractClonable
>> {
>> public:
>> AbstractClonable* clone() const
>> {
>> Base* result = doClone() ;
>> assert( typeid( result ) == typeid( *this ) ) ;
>> return result ;
>> }

>> private:
>> virtual Base* doClone() const = 0 ;
>> } ;

> I actually find your code quite useful. However, I do find an


> issue, when the class hierarchy is as follows:

> class A : public public AbstractClonable<A> {};
> class B : public Clonable<B, A> {};
> class C : public Clonable<C, A> {}'

> B* p = new C();

> Then p->clone() will really return a B object instead of C.

Good point.

I guess in this case, you need to put the actual implementation
in the virtual function, and have the non-virtual call the
virtual in the derived classes. (The basic idiom is from Barton
and Nackman -- the goal was different, and the context as well.)

> The solutions I find so far are:

> 1) Declare only leaf-node objects can be instantiated and let B inherit
> from A instead of Clonable<B, A>;
> 2) Change Clonable::clone() to this effect:

> Derived* clone() const
> {
> if (typeid(Derived) == typeid(*this))
> return new Derived(*static_cast<const Derived*>(this));
> else
> return static_cast<Derived*>(doClone());
> }

> Any comments?

What's wrong with just:

Derived* clone() const
{
Derived* result = doClone() ;
assert( typeid( *result ) == typeid( *this ) ) ;
return result ;
}

// ...
Derived* doClone() const


{
return new Derived( *dynamic_cast< Derived const* >( this ) ) ;
}

> Any better solutions? Anyone knows about the overhead of
> typeid?

Fairly high, I believe. But then, so is the overhead of dynamic
allocation and in many cases, of the copy constructor.

--
James Kanze kanze...@neuf.fr


Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung

9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34

Wu Yongwei

unread,
Jun 13, 2006, 6:34:28 PM6/13/06
to
James Kanze wrote:

> Wu Yongwei wrote:
> > Derived* clone() const
> > {
> > if (typeid(Derived) == typeid(*this))
> > return new Derived(*static_cast<const Derived*>(this));
> > else
> > return static_cast<Derived*>(doClone());
> > }
>
> > Any comments?
>
> What's wrong with just:
>
> Derived* clone() const
> {
> Derived* result = doClone() ;
> assert( typeid( *result ) == typeid( *this ) ) ;
> return result ;
> }
>
> // ...
> Derived* doClone() const
> {
> return new Derived( *dynamic_cast< Derived const* >( this ) ) ;
> }

Nothing wrong: it is better (though clone() is now always virtual; but
it is always virtual in Java too).

> > Any better solutions? Anyone knows about the overhead of
> > typeid?
>
> Fairly high, I believe. But then, so is the overhead of dynamic
> allocation and in many cases, of the copy constructor.

After some investigation, now I believe that at least in some compilers
typeid is efficient; typeid(A) == typeid(B) can be as good as A.vtbl[0]
== B.vtbl[0].

Best regards,

Yongwei

Alf P. Steinbach

unread,
Jun 14, 2006, 6:26:42 PM6/14/06
to
* Wu Yongwei:

> James Kanze wrote:
>> Wu Yongwei wrote:
>>> Derived* clone() const
>>> {
>>> if (typeid(Derived) == typeid(*this))
>>> return new Derived(*static_cast<const Derived*>(this));
>>> else
>>> return static_cast<Derived*>(doClone());
>>> }
>>> Any comments?
>> What's wrong with just:
>>
>> Derived* clone() const
>> {
>> Derived* result = doClone() ;
>> assert( typeid( *result ) == typeid( *this ) ) ;
>> return result ;
>> }
>>
>> // ...
>> Derived* doClone() const
>> {
>> return new Derived( *dynamic_cast< Derived const* >( this ) ) ;
>> }
>
> Nothing wrong: it is better (though clone() is now always virtual; but
> it is always virtual in Java too).

Huh? dynamic_cast to pointer type doesn't throw an exception on
failure, it produces a nullpointer. Dereferencing that nullpointer &
accessing the result, as is potentially done in doClone, is very UB.

I haven't followed the thread but also what I see of the original code
above, looks very wrong/unsafe.

Cloning is a typical example of the sort of problem -- repeated glue &
pattern code -- that templates were supposed to help with, but don't,
really. We have the same with visitor pattern (which I think is much
more important than cloning, but both are important). We need Andrei,
or someone, to come up with a brilliant solution.

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

kanze

unread,
Jun 15, 2006, 11:02:06 AM6/15/06
to
Alf P. Steinbach wrote:
> * Wu Yongwei:
> > James Kanze wrote:
> >> Wu Yongwei wrote:
> >>> Derived* clone() const
> >>> {
> >>> if (typeid(Derived) == typeid(*this))
> >>> return new Derived(*static_cast<const Derived*>(this));
> >>> else
> >>> return static_cast<Derived*>(doClone());
> >>> }
> >>> Any comments?
> >> What's wrong with just:
> >>
> >> Derived* clone() const
> >> {
> >> Derived* result = doClone() ;
> >> assert( typeid( *result ) == typeid( *this ) ) ;
> >> return result ;
> >> }
> >>
> >> // ...
> >> Derived* doClone() const
> >> {
> >> return new Derived( *dynamic_cast< Derived const* >( this ) ) ;
> >> }

> > Nothing wrong: it is better (though clone() is now always
> > virtual; but it is always virtual in Java too).

> Huh? dynamic_cast to pointer type doesn't throw an exception
> on failure, it produces a nullpointer. Dereferencing that
> nullpointer & accessing the result, as is potentially done in
> doClone, is very UB.

Good point. In this particular case, we are safe IF the class
is used correctly (and compile time asserts can be used to
ensure that it is used correctly), but it's a bad habit to get
into.

> I haven't followed the thread but also what I see of the
> original code above, looks very wrong/unsafe.

Well, the code is (or was meant to be) part of a template class:

template< typename Derived, typename Base >
class Clonable : public Base
{

// ...
} ;

which is intended to be used:

class MyDerived : public Clonable< MyDerived, MyBase > { ... } ;

The class Clonable should have some sort of concept check to
ensure that it is only used as a base class to Derived, but I'm
not sure how to implement this. It's easy to check that Derived
really does derived from Clonable< Derived, Base >, but the most
frequent cause of error is probably due to using copy/paste to
create a SecondDerived from MyDerived, and forgetting to correct
the name in the template instantiation.

So you're definitly right: it should be:

Derived*
doClone() const
{
Derived const* tmp
= dynamic_cast< Derived const* >(this ) ;
assert( tmp != NULL ) ;
return new Derived( *tmp ) ;
}

> Cloning is a typical example of the sort of problem --
> repeated glue & pattern code -- that templates were supposed
> to help with, but don't, really. We have the same with
> visitor pattern (which I think is much more important than
> cloning, but both are important). We need Andrei, or someone,
> to come up with a brilliant solution.

Some sort of concept check that Clonable< Derived, Base > is not
used other than as an immediate base of Derived would be what
the doctor ordered here. But off hand, I don't know how to do
it. And what can you do in Base to prevent Derived from
deriving directly from Base, without an intermediate Clonable?

I might add that I did the above more as an intellectual
exercise than as a potential pragmatic solution. Unless you
really can somehow impose that the correct instantiation of
Clonable is a direct base of all of the classes in the
hierarchy, and make it work with all of the configurations of
multiple inheritance, it just gives a false sense of security.
And doing all that is probably a lot more work than it is worth.
My experience has been that people don't forget to override
clone() when they're supposed to, and that if they do, code
review will certainly catch it. Adding a run-time check in the
base class is already at the limit as to the amount of work
worth investing for error catching here, and anything more is
too much.

--
James Kanze GABI Software

Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung

9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Wu Yongwei

unread,
Jun 17, 2006, 8:07:37 PM6/17/06
to
Alf P. Steinbach wrote:
> * Wu Yongwei:
> > James Kanze wrote:
> >> Wu Yongwei wrote:
> >>> Derived* clone() const
> >>> {
> >>> if (typeid(Derived) == typeid(*this))
> >>> return new Derived(*static_cast<const Derived*>(this));
> >>> else
> >>> return static_cast<Derived*>(doClone());
> >>> }
> >>> Any comments?
> >> What's wrong with just:
> >>
> >> Derived* clone() const
> >> {
> >> Derived* result = doClone() ;
> >> assert( typeid( *result ) == typeid( *this ) ) ;
> >> return result ;
> >> }
> >>
> >> // ...
> >> Derived* doClone() const
> >> {
> >> return new Derived( *dynamic_cast< Derived const* >( this ) ) ;
> >> }
> >
> > Nothing wrong: it is better (though clone() is now always virtual; but
> > it is always virtual in Java too).
>
> Huh? dynamic_cast to pointer type doesn't throw an exception on
> failure, it produces a nullpointer. Dereferencing that nullpointer &
> accessing the result, as is potentially done in doClone, is very UB.

My comment was on the code structure, not dynamic_cast. In fact, my
local code all uses static_cast in such cases, for efficiency.

As James pointed out, there would be no problems if the code was used
right. His code is safer when used not right, since the UB is really a
core dump or GPF in most systems and will leave useful info for
debugging. I do not think assert is better in real worlds.

Best regards,

Yongwei

dav...@webmaster.com

unread,
Jun 20, 2006, 6:01:55 AM6/20/06
to
Ulrich Eckhardt wrote:

> [idea 1: using dynamic_cast to detect the type and create a copy of it]

> [idea 2:]
> > struct A
> > { virtual A* clone() { return new A(*this);} ... };
> >
> > struct B : public A
> > { virtual B* clone() { return new B(*this);} ... };

> Not that I'd know. I'd like to point out that both methods have their


> advantages. The first is usually considered a greater cludge than the
> second and "less OO", but it offers all related code in one place and is
> nonintrusive. It could have been improved by not using dynamic_cast but
> using typeid() and comparing for equality - this has the great advantage
> that when class D, derived from B, is added it doesn't trigger the code
> that copies a B.

I would think that if D didn't have its own copy logic, that would be
what you'd want.

For example, suppose you have a class hierarchy like
Client->WebClient->SpecificWebClient. Perhaps there cannot concurrently
exist two identical SpecificWebClient (since it's associated with a TCP
connection which you cannot duplicate).

In this case, the logical thing to do if someone tries to duplicate a
SpecificWebClient is to return a WebClient, giving him a new client of
the same type. Obviously, you cannot give him precisely the same
client.

It is not unusual to have a hierarchy that includes duplicatable and
non-duplicatable objects. Duplicating a non-duplicatable object should
give you the first object up that is duplicatable.

Before anyone says that because a SpecificWebClient is not
duplicatable, you should not be able to duplicate it, remember that a
SpecificWebClient *is* a WebClient, and WebClients *are* duplicatable,
giving you another WebClient that may or may not also be a
SpecificWebClient. SpecificWebClients are not duplicatable in the sense
that duplicating them cannot produce a SpecificWebClient because the
connection is inherenently incapable of being duplicated.

Similar logic applies to any class trees where sufficiently derived
classes cannot be duplicated but higher-up classes can. Consider, for
example:
PublicKey->RSAPublicKey->RSAPublicKeyOnCryptoToken. Some code might
Duplicate an RSAPublicKey to get one to hold onto for awhile, but you
don't want to duplicate the key on the token and you may not want two
objects referring to the same key on the same token. So you either
don't implement 'Duplicate' on 'RSAPublicKeyOnCryptoToken' or make it
return an 'RSAPublicKey'.

We deal with many types of objects that are inherently not duplicatable
and where it does not make sense to duplicate to another object that
refers to the same underlying object.

DS

werasm

unread,
Jun 20, 2006, 9:54:42 AM6/20/06
to
DS wrote:
> For example, suppose you have a class hierarchy like
> Client->WebClient->SpecificWebClient. Perhaps there cannot
> concurrently
> exist two identical SpecificWebClient (since it's associated with a
> TCP
> connection which you cannot duplicate).

In this case, I would use a weaker association to the TCP connection,
that allows it to be shareable between the <SpecificWebClient>
instances, making them duplicatable (like having member
boost::shared_ptr<TcpConn> in SpecificWebClient. The base class (or
interface) makes certain promises to its client. One of those promises
is that it gives an exact duplicate (of type as well). Duplicate will
be called via Base interface and the caller is not required to know the
real type, but he expects the duplicated type to behave just like the
original type when the rest of the interface is used - does it (can it)
do this when duplicate returns a type higher in the hierarchy? Also, in
proper designed code I would assume that WebClient is abstract (and
non-duplicatable too as it is not-instantiatable). What are you do
return - throw (my fat) interface_not_implemented?

>
> In this case, the logical thing to do if someone tries to duplicate a
> SpecificWebClient is to return a WebClient, giving him a new client of
> the same type. Obviously, you cannot give him precisely the same
> client.

Well, according to your hierarchy, SpecificWebClient is a WebClient and
not visa versa. Therefore you are not giving the caller a client of the
same type. This certainly does not seem like the logical thing to me.
The logical thing to do will be for the derived class (having an isA
relationship with its base) to provide the implementation iaw. the
interface requirements imposed by its base, or not derive at all, or
use some mechanism of sharing of members that allows for duplication.

>
> It is not unusual to have a hierarchy that includes duplicatable and
> non-duplicatable objects. Duplicating a non-duplicatable object should
> give you the first object up that is duplicatable.

In my opinion, it is unusual and wrong. If this is the case, I smell
rotten design.

>
> Before anyone says that because a SpecificWebClient is not
> duplicatable, you should not be able to duplicate it, remember that a
> SpecificWebClient *is* a WebClient, and WebClients *are* duplicatable,
> giving you another WebClient that may or may not also be a
> SpecificWebClient. SpecificWebClients are not duplicatable in the
> sense
> that duplicating them cannot produce a SpecificWebClient because the
> connection is inherenently incapable of being duplicated.

Not if one has a weak relation with a connection, or if a connection
can be shared amongst objects that were duplicated.

>
> Similar logic applies to any class trees where sufficiently derived
> classes cannot be duplicated but higher-up classes can. Consider, for
> example:
> PublicKey->RSAPublicKey->RSAPublicKeyOnCryptoToken. Some code might
> Duplicate an RSAPublicKey to get one to hold onto for awhile, but you
> don't want to duplicate the key on the token and you may not want two
> objects referring to the same key on the same token. So you either
> don't implement 'Duplicate' on 'RSAPublicKeyOnCryptoToken' or make it
> return an 'RSAPublicKey'.

...Or you have duplicatable and non-duplicatable keys in your hierarcy,
both types being on the same level. OTOH, a key can be non-duplicatable
by default, as this seems to be the case in your example. A
duplicatable key can be a special case. If you don't want to
re-implement certain functionality, make use of implementation classes
that contain the implementation (and algorithms), and forward call
their member functions to adhere to the interface imposed by the
interface (prefer aggregation or containment to inheritance, prefer
weakest relationship - ring a bell?).

One could even use a template interface to specify whether or not
something is duplicatable.

template <bool Dup>
class Key
{
virtual Key* Dup() const = 0;
//...
};
template <>
class Key<false>
{
//Dup doesn't exists as part of this interface...
};

I suppose better alternatives exist, but you get the idea

Key<duplicatable> <----- Its hierarchy
Key<non-duplicatable> <----- Its hierachy

Now we don't impose requirements on our derived that they can't
enforce.

>
> We deal with many types of objects that are inherently not
> duplicatable
> and where it does not make sense to duplicate to another object that
> refers to the same underlying object.

Then don't derive from a base class that imposes that constraint (to be
duplicatable) on the derived's implementation, or make another plan.

Regards,

Werner

0 new messages