Topic: Generic clone
Author: "terminator(jam)" <farid.mehrabi@gmail.com>
Date: Thu, 23 Aug 2007 09:55:09 CST Raw View
On Aug 21, 8:41 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
> On Aug 21, 5:27 am, Simon Richter <Simon.Rich...@hogyros.de> wrote:
>
> > Hi,
>
> > occasionally, I have wished for a way to create a copy of a polymorphic
> > object that I do not know the dynamic type of, i.e. the infamous clone()
> > method.
>
> > Obviously, having one in those objects where I need it is rather simple:
> > just have the base class define a virtual method that returns a pointer
> > to the newly constructed object -- however there are a few things I find
> > rather ugly about this
>
> [...]
>
> You're also forgetting one thing that makes it ugly:
>
> Your clone function is responsible for allocating your memory. That's
> where the root of the ugliness lies. A virtual constructor should just
> be a way to construct, not be a way to allocate memory, construct, and
> then return a pointer to that memory that will be handled by who knows
> who.
> Plus not everyone may want to use new to allocate your object, because
> they need to allocate it in a special place or because they need to
> allocate it in a special pool. (think embedded systems, kernel
> programming, etc.)
>
> However, the callee doesn't have enough information to know how to
> allocate the object: it needs to know its size, and eventually its
> alignment.
>
> Here is then a possible solution:
>
> struct Base
> {
> virtual void construct(void* p) const
> {
> new(p) Base();
> }
>
> virtual void clone(void* p) const
> {
> new(p) Base(*this);
> }
>
> virtual std::pair<std::size_t, std::size_t> size_and_align() const
> {
> return std::make_pair(sizeof Base, alignof Base);
> }
>
> };
>
> struct Derived : public Base
> {
> void construct(void* p) const
> {
> new(p) Derived();
> }
>
> void clone(void* p) const
> {
> new(p) Derived(*this);
> }
>
> virtual std::pair<std::size_t, std::size_t> size_and_align() const
> {
> return std::make_pair(sizeof Derived, alignof Derived);
> }
>
> };
>
> There could be more overloads of 'construct' for other constructors.
>
> Unfortunetely, the copy-constructor more or less has to be treated
> separately to prevent confusion between Derived(Derived) and
> Derived(Base). There is room for discussion here though.
>
> It could be interesting to have the compiler automatically generates
> those virtual "magic" functions when we tag constructors as being
> virtual. (size_and_align could actually always be there)
>
> Then, we could do:
>
> template<typename T>
> T* new_clone(const T& t)
> {
> T* tmp = ::operator new(t.size_and_align().first);
>
> try
> {
> t.clone(tmp);
> }
> catch(...)
> {
> ::operator delete(tmp);
> throw;
> }
>
> return tmp;
>
> }
>
> int main()
> {
> std::unique_ptr<Base> b = new Derived;
> std::unique_ptr<Base> b2 = new_clone(*b);
>
> }
>
I think somewhere in the code you are upward/void casting which
discards static(compile time) info about the data:
ret_type my_func(void *);
class my_base;
ret_type my_base_func(my_base*);
class my_derived:my_base{
....//etc
};
my_code(){
my_derived my_obj;
my_func(&obj);//implicit void cast
my_base_func(&obj);//implicit upward cast
};
I suggest that you pack the static stuff in a runtime wrapper class
and send that class to the callee :
#include <typeinfo>
namespace FM_RTTI{
//declare dynamic interface:
struct abstract_wrapper{
virtual void* get () const =0;
virtual char* classname () const =0;
virtual void* clone () const =0;//allocate and copy
virtual void* create () const =0;//allocate and
default construct
virtual void dispose (void*) const =0;//destruct and
deallocate
virtual size_t size () const =0;
virtual void* shadow(void*) const =0;//mere copy to
pre allocated location
virtual void* zero (void*) const =0;//mere default
construct to pre allocated location
virtual void finalize (void*) const =0;//merely
destruct and leave it to be deallocated
// virtual void* swap (void*) const =0;
virtual void* assign_to (void*) const =0;
virtual ~abstract_wrapper() =0;
};
//reflect compile time data at runtime:
template <typename Data>
struct generic_wrapper:
abstract_wrapper
{
//abstract runtime interface:
Data* get() const{return me;};
char* classname () const {return typeid(Data).name;};
Data* clone () const {return new Data(*me);};
Data* create () const{return new Data;};
void dispose (void* ptr) const {delete
dynamic_cast<Data*>(ptr);};
size_t size () const {return sizeof(Data);};
Data* shadow(void* ptr) const {return new(ptr)
Data(*me)};
Data* zero (void* ptr) const{return new(ptr) Data;};
void finalize (void* ptr) const
{(dynamic_cast<Data*>(ptr))->~Data();};
/* Data* swap (void* ptr) const{
Data* const
dataptr=dynamic_cast<Data*>(ptr);
swap(*dataptr,*me);
return dataptr;
};
*/
Data* assign_to (void* ptr) const{
Data* const
dataptr=dynamic_cast<Data*>(ptr);
(*dataptr)=*me;
return dataptr;
};
//normal static interface:
generic_wrapper(Data *ptr): me(ptr){
if( typeid(Data)!=typeid(*ptr) ) throw
bad_cast;
};
Data& operator*() {return *me;};
Data* operator->() {return me;};
private:
Data* me;
};
template<typename Data>
generic_wrapper<Data> auto_wrap(Data *ptr){return ptr;};
};
using namespace FM_RTTI;
ret_type my_func(const abstract_wrapper& aw){
void* original=aw.get();//never delete or any sort of
destruction
void* my_clone=aw.clone();//destroy with dispose
....//etc
aw.dispose(my_clone);//created via clone or create
my_clone = new char[aw.size()];//to be 'delete'd
void* clone_ptr =aw.shadow(my_clone);//very bad,alignment may
fail;must be 'finalize'd
....//etc
aw.finalize(my_clone);//created through shadow or zero
....//do something with my_clone/clone_ptr
delete[] myclone;//created with new
};
struct Base;
struct Derive:Base{
....//etc
};
my_code(){
Derived my_obj;
my_func(auto_wrap(&my_obj));//ok
Base* bptr=&my_obj;
my_func(auto_wrap(&bptr));/*oops:silent error for static types
and runtime badcast for polymorphic ones*/
my_func(auto_wrap(dynamic_cast<Derived *>(bptr) ) );/* ok with
polymorphic types ,error prone with static ones*/
};
I got too lazy to consider all the details but one can fix it.
regards,
FM.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Simon Richter <Simon.Richter@hogyros.de>
Date: Thu, 23 Aug 2007 18:08:10 CST Raw View
Hi,
Mathias Gaunard wrote:
> On Aug 21, 5:27 am, Simon Richter <Simon.Rich...@hogyros.de> wrote:
>> occasionally, I have wished for a way to create a copy of a
>> polymorphic object that I do not know the dynamic type of, i.e. the
>> infamous clone() method.
> Your clone function is responsible for allocating your memory. That's
> where the root of the ugliness lies. A virtual constructor should just
> be a way to construct, not be a way to allocate memory, construct, and
> then return a pointer to that memory that will be handled by who knows
> who.
Indeed, however it cannot be properly done using the standard
allocators; this would require virtual methods with "free" template
parameters (for the allocator), which cannot be done. If you are using
clone(), you have left the realm of allocators anyway, as you need to
destruct your objects with delete then, and only overriding operator
delete will help here.
The language extension I proposed in my original posting can be used to
retrieve size and alignment requirements; allocation will have to happen
through operator new anyway, though.
> Plus not everyone may want to use new to allocate your object, because
> they need to allocate it in a special place or because they need to
> allocate it in a special pool. (think embedded systems, kernel
> programming, etc.)
Being an embedded systems programmer, I'm usually the first to jump into
peoples' faces for not using allocators; in this particular instance, it
is not possible, however, since the allocator needs to know the type of
object instantiated statically.
> template<typename T>
> T* new_clone(const T& t)
> {
> T* tmp = ::operator new(t.size_and_align().first);
No. This goes around the type-specific operator new, so deleting the
object would invoke UB.
terminator(jam) wrote:
> generic_wrapper(Data *ptr): me(ptr){
> if( typeid(Data)!=typeid(*ptr) ) throw
> bad_cast;
> };
This at least makes the slicing condition runtime detectable, which is a
certain improvement.
> void* my_clone=aw.clone();//destroy with dispose
> ....//etc
This means that I will have to carry around the pointer to the abstract
wrapper along with the pointer to the object itself?
Simon
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Mathias Gaunard <loufoque@gmail.com>
Date: Fri, 24 Aug 2007 09:53:44 CST Raw View
On Aug 24, 2:08 am, Simon Richter <Simon.Rich...@hogyros.de> wrote:
About the allocators, I believe the standard allocators are simply too
high-level.
malloc-like allocators is what I meant.
> > template<typename T>
> > T* new_clone(const T& t)
> > {
> > T* tmp = ::operator new(t.size_and_align().first);
>
> No. This goes around the type-specific operator new, so deleting the
> object would invoke UB.
Indeed, the new_clone function is ugly.
The construct, clone, size_and_align stuff that I proposed are nothing
but low-level details: they are not meant to be used as-is but only by
wrappers. As such, they aim to do as little as possible and be highly
efficient. Introducing RTTI checks only means runtime overhead.
The most elegant wrapper would be to write something like
poly_obj<Base> b1 = Derived1(); // or in_place<Derived1>() for
efficiency
poly_obj<Base> b2 = b1; // does a deep-copy
This is perfectly safe and provides for value-based programming with
polymorphic objects.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "terminator(jam)" <farid.mehrabi@gmail.com>
Date: Sat, 25 Aug 2007 15:37:20 CST Raw View
On Aug 21, 8:41 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
> template<typename T>
> T* new_clone(const T& t)
> {
> T* tmp = ::operator new(t.size_and_align().first);
>
> try
> {
> t.clone(tmp);
> }
> catch(...)
> {
> ::operator delete(tmp);
> throw;
> }
>
> return tmp;
>
> }
>
the disadvantage is to lose the ability to call proper new/delete
operators for the types that overlaod them;If one intends to keep
them, he can`t device this aproach.
about 'alignof': is it standard?
regards,
FM.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "terminator(jam)" <farid.mehrabi@gmail.com>
Date: Sat, 25 Aug 2007 20:12:26 CST Raw View
> terminator(jam) wrote:
> > generic_wrapper(Data *ptr): me(ptr){
> > if( typeid(Data)!=typeid(*ptr) ) throw
> > bad_cast;
> > };
>
> This at least makes the slicing condition runtime detectable, which is a
> certain improvement.
>
> > void* my_clone=aw.clone();//destroy with dispose
> > ....//etc
>
> This means that I will have to carry around the pointer to the abstract
> wrapper along with the pointer to the object itself?
>
my_clone is a new object (a clone of the real one),you can play with
return types(E.G. abstract_wrapper::abstract_wrapper &whatever(etc)
and generic_wrapper::abstract_wrapper *&whatever(etc) instead of
(void*) and (Data*)) .and there is a minimal memory overhead of
wraping a pointer in a function pack.you will only need a pointer/
reference to the runtime interface(abstract_wrapper) but a minimal
runtime overhead is essential to get a void pointer to the original
object throughout 'void* abstract_wrapper::get() ' ,in case you need
the original object.you can also separate the interface from the
object and use a function with two arguments(no 'me' in
'generic_wrapper'):
ret_type my_func(const abstract_wrapper& aw,void* ptr);
but automation is decreased and you will need to increase the number
of arguments to wrapper classes` methods.
regards,
FM.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "terminator(jam)" <farid.mehrabi@gmail.com>
Date: Sat, 25 Aug 2007 20:12:25 CST Raw View
On Aug 24, 6:53 pm, Mathias Gaunard <loufo...@gmail.com> wrote:
> On Aug 24, 2:08 am, Simon Richter <Simon.Rich...@hogyros.de> wrote:
>
> About the allocators, I believe the standard allocators are simply too
> high-level.
> malloc-like allocators is what I meant.
>
> > > template<typename T>
> > > T* new_clone(const T& t)
> > > {
> > > T* tmp = ::operator new(t.size_and_align().first);
>
> > No. This goes around the type-specific operator new, so deleting the
> > object would invoke UB.
>
> Indeed, the new_clone function is ugly.
> The construct, clone, size_and_align stuff that I proposed are nothing
> but low-level details: they are not meant to be used as-is but only by
> wrappers. As such, they aim to do as little as possible and be highly
> efficient. Introducing RTTI checks only means runtime overhead.
>
> The most elegant wrapper would be to write something like
>
> poly_obj<Base> b1 = Derived1(); // or in_place<Derived1>() for
> efficiency
> poly_obj<Base> b2 = b1; // does a deep-copy
>
> This is perfectly safe and provides for value-based programming with
> polymorphic objects.
>
I need ask for more details if you do not mind.
thanks in advance,
FM.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Mathias Gaunard <loufoque@gmail.com>
Date: Sun, 26 Aug 2007 15:00:39 CST Raw View
On 26 ao t, 04:12, "terminator(jam)" <farid.mehr...@gmail.com> wrote:
> I need ask for more details if you do not mind.
Details about what?
You want an implementation of the poly_obj I was talking about?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Simon Richter <Simon.Richter@hogyros.de>
Date: Mon, 20 Aug 2007 21:27:02 CST Raw View
Hi,
occasionally, I have wished for a way to create a copy of a polymorphic
object that I do not know the dynamic type of, i.e. the infamous clone()
method.
Obviously, having one in those objects where I need it is rather simple:
just have the base class define a virtual method that returns a pointer
to the newly constructed object -- however there are a few things I find
rather ugly about this:
- It's repetitive.
Every clonable class needs to define its own clone() method, which looks
nearly the same everywhere.
- It's prone to slicing
If someone derives from a concrete class that has a clone() method,
there is no warning or hint of any kind if they forget to override
clone() (or override it with different constness).
- The (derived's) clone() method cannot return an auto_ptr<derived>
While that is not a big problem in practice (after all, storing the
returned pointer in an auto_ptr immediately is just as safe, as it is
pretty unlikely that returning a pointer generates an exception), I'd
still like to be able to define my method so that it returns an
auto_ptr, so people cannot accidentally forget to take over the value.
Now, a language feature like
base *a = new derived;
base *b = new(*a); // syntax debateable
would present all interesting kinds of problems, since there is no way
to decide in the second line whether the object should in fact be
cloneable or even copyable, so this would lead to a new type of
exception being generated in the runtime if it isn't; also all
polymorphic objects would be burdened with a clone function in the
vtable whether it is used or not. So that is not a good idea.
So, let me propose a slightly more difficult syntax:
class base
{
public:
template<typename T>
virtual T *T::clone(void)
{
return new T(*this);
}
};
Obviously, this doesn't conflict with any current code -- "template" and
"virtual" are seldom seen together these days, and method names with
"::" in them are also generally frowned upon[1] The obvious failure to
instantiate when base is abstract is not a problem -- all derivers are
guaranteed to implement this method, so noone would ever use this one
anyway. The different return types would have to be picked up as
covariant returns, but at the point of instantiation (vtable generation
for the derived class) we generally have all the information we need.
Being able to return an auto_ptr would be slightly more difficult:
class base
{
public:
template<typename T>
std::auto_ptr<T> T::clone(void)
{
return std::auto_ptr<T>(this->clone_private());
}
private:
template<typename T>
virtual T *T::clone_private(void)
{
return new T(*this);
}
};
The nonvirtual function can be instantiated even on abstract types, and
uses the static type as the template parameter T, as nothing else is
known about the type at this point; the return type from clone() is the
auto_ptr to the static type, which is what we can guarantee here[2]
Best of all, the above definition of "base" can be named "std::clonable"
and everyone who wants their class to be clonable can simply derive
from that without having to know the icky details[3].
Comments?
Simon
[1] That is another problem
[2] This is not to discourage anyone from making covariant returns of
convertible types possible
[3] which would probably include some interesting questions on the
visibility of clone_private()
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Mathias Gaunard <loufoque@gmail.com>
Date: Tue, 21 Aug 2007 11:41:20 CST Raw View
On Aug 21, 5:27 am, Simon Richter <Simon.Rich...@hogyros.de> wrote:
> Hi,
>
> occasionally, I have wished for a way to create a copy of a polymorphic
> object that I do not know the dynamic type of, i.e. the infamous clone()
> method.
>
> Obviously, having one in those objects where I need it is rather simple:
> just have the base class define a virtual method that returns a pointer
> to the newly constructed object -- however there are a few things I find
> rather ugly about this
[...]
You're also forgetting one thing that makes it ugly:
Your clone function is responsible for allocating your memory. That's
where the root of the ugliness lies. A virtual constructor should just
be a way to construct, not be a way to allocate memory, construct, and
then return a pointer to that memory that will be handled by who knows
who.
Plus not everyone may want to use new to allocate your object, because
they need to allocate it in a special place or because they need to
allocate it in a special pool. (think embedded systems, kernel
programming, etc.)
However, the callee doesn't have enough information to know how to
allocate the object: it needs to know its size, and eventually its
alignment.
Here is then a possible solution:
struct Base
{
virtual void construct(void* p) const
{
new(p) Base();
}
virtual void clone(void* p) const
{
new(p) Base(*this);
}
virtual std::pair<std::size_t, std::size_t> size_and_align() const
{
return std::make_pair(sizeof Base, alignof Base);
}
};
struct Derived : public Base
{
void construct(void* p) const
{
new(p) Derived();
}
void clone(void* p) const
{
new(p) Derived(*this);
}
virtual std::pair<std::size_t, std::size_t> size_and_align() const
{
return std::make_pair(sizeof Derived, alignof Derived);
}
};
There could be more overloads of 'construct' for other constructors.
Unfortunetely, the copy-constructor more or less has to be treated
separately to prevent confusion between Derived(Derived) and
Derived(Base). There is room for discussion here though.
It could be interesting to have the compiler automatically generates
those virtual "magic" functions when we tag constructors as being
virtual. (size_and_align could actually always be there)
Then, we could do:
template<typename T>
T* new_clone(const T& t)
{
T* tmp = ::operator new(t.size_and_align().first);
try
{
t.clone(tmp);
}
catch(...)
{
::operator delete(tmp);
throw;
}
return tmp;
}
int main()
{
std::unique_ptr<Base> b = new Derived;
std::unique_ptr<Base> b2 = new_clone(*b);
}
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]