$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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.