|
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