Boost logo

Boost :

From: Mathias Gaunard (mathias.gaunard_at_[hidden])
Date: 2007-09-02 18:52:51


I am proposing the addition of the following very simple utility in
Boost, and thus request a very informal and early review of the ideas
and design.

Idea

----
The idea is to provide an utility that allows the manipulation of 
dynamically typed objects, that may be of any derived type of a given 
base type, as if it were a simple base object.
This is quite similar to a smart pointer that does deep-copying, but 
without any pointer or memory allocation exposed.
As code, it appears as such:
poly_obj<Base> b = Derived();
poly_obj<BaseBase> b2 = b; // b2 is a copy of b (Derived::Derived(const 
Derived&) was called), implicit upcasting
Motivation
----------
The motivation for this is that often usage of dynamic typing with 
inheritance and inclusion polymorphism often means explicit memory 
allocation and deallocation, and thus are often a source of unsafety.
shared_ptr is one way to solve the problem, yet the shared object 
doesn't behave as a normal C++ object: it has entity semantics (= 
reference) instead of value semantics (= copying).
shared_ptr is highly regarded as being one of the most useful utilities 
in boost by neophytes because it allows easier and simpler programming, 
without the usual issues in regards with pointers people are used to.
However it might not always be the right solution, and I believe I have 
seen it being used even when sharing wasn't needed at all.
As a matter of fact, it appears that sharing is rarely needed, and makes 
the program more difficult to understand. (sharing between threads also 
cause a few issues). Indeed, the object is freed when it is no longer 
used, but it's not always trivial to trace whether it's still used or 
not. Sharing also means you can expect side-effects coming from anywhere.
Pure value objects make the program more easy to understand by making it 
more deterministic and thus to maintain.
Value semantics is what most of the standard library is built upon. And 
thus it makes more sense to use the standard containers with poly_obj 
than with shared_ptr, because this is kind of semantics they were 
designed for.
Design
------
To implement that utility, there is a need for a way to call the 
appropriate copy constructor and move constructor for the right types, 
even with type erasure.
There is no standard way, however, to simply make constructors virtual, 
so there is a need to do such a thing.
A few implementations of clone_ptr, a similar idea, that I have seen 
tried to make this non-intrusive.
This of course adds some overhead, mainly in size of the object, but it 
has another downside: it requires to assume that the types of the 
objects being inserted in it are their real types.
Thus, writing that kind of code:
poly_obj<Base> b = poly_obj<Base>(Derived()).get();
Would produce type splitting.
(the get() member function returns a real C++ reference to the object)
I believe this is not really a good thing. And the only way to fix that 
is to make the virtuality of the constructors intrusive.
I thus came up with the following design:
class Foo
{
    ...
public:
     virtual void clone(void* p) const
     {
         return new(p) Foo(*this);
     }
     virtual std::pair<std::size_t, std::size_t> size_and_align() const
     {
         return std::make_pair(sizeof(Foo), 
boost::alignment_of<Foo>::value);
     }
};
(Of course, a 'mixin' can be used to not have to repeatly rewrite such 
code for every class)
It should be quite important to stabilize that interface, so remarks are 
welcome.
It is judged very important that the user should be able to define how 
the memory is allocated. Since virtual templates aren't possible, an 
allocator cannot be passed to the 'clone' member function.
The solution I came up with allows to clone the object on already 
initialized memory, and provides the necessary information to know what 
that memory should be like.
It is, however, still not really possible to use standard allocators, 
which need to know the type.
An additional member for moving might also be needed.
Code
----
Some initial code is available on the vault, in "Utilities".
http://www.boost-consulting.com/vault/index.php?direction=0&order=&directory=Utilities
Note that the tests are just here to show how it's to be used, and are 
not real tests at all.
No support for moving yet, and no support for using custom allocators 
either (since an allocator interface would have to be agreed upon, first).
operator= also needs a facility to compare types, and thus it uses RTTI. 
Since I've seen a few times people who complained about the usage of 
RTTI, there is also a hack to replace it.

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk