|
Boost : |
From: Corrado Zoccolo (czoccolo_at_[hidden])
Date: 2007-10-20 08:53:57
Hi
Following the recent interest in clone pointers and Polymorphic value object
wrappers, I developed a slightly feature enriched Polymorphic Value object,
that implements the small object optimization, and models the Optional
Pointee concept. It follows a non-intrusive approach to cloning.
The code can be found in the boost vault: *http://tinyurl.com/3a6sq8*
Motivation:
Dynamic polymorphism is a valuable but underestimated tool to the modern C++
programmer.
It for example allows the implementation of type erasure and other
techniques for physical decoupling.
It is also unavoidable every time dynamic dispatching is needed.
Unfortunately solutions based on it may often be slow, because traditional
dynamic polymorphism is always paired with dynamic allocation.
The proposed tool is targeted to ease the usage of dynamic polymorphism in
modern C++ context, by giving value semantics to polymorphic objects, as
well as providing a well known optimization technique to avoid dynamic
allocation when the involved objects are small.
Dynamic polymorphism, value semantics and small object optimization
constitute a powerful receipt to solve such problems. As evidence
boost::function, among the most valuable tools for type erasure and physical
decoupling available in boost, is using all three of them.
This tool is meant to be a low level tool, on which powerful abstractions
like boost::function could be built easily.
Basic Usage Example:
#include "optional_poly.hpp"
#include <iostream>
// class with one virtual method but not virtual destructor
// optional_poly will know the derived type, so it will call the correct
destructor nevertheless
struct B { virtual operator int() {return 0;}}; // lack of virtual
destructor is intentional
// Derived class, with side effect in destructor to show functionality, and
overloaded virtual method to determine which one was called
struct D : B {
operator int() {return 1;}
~D() { std::cerr<<"Got it\n"; }
void swap(D&) {}
private:
void operator=(D const &);
};
// swap reachable via ADL. optional_poly will prefer (by default) this to
implement its swap method. Using a trait, you can specify if a swap method
or no swap at all should be used for a given type
void swap(D& a,D& b) {
a.swap(b);
}
int main() {
// optional_poly constructor and operator= require that the static type
match the dynamic one.
// The following commented code is invalid, and will assert at runtime
// const B& d=D();
// boost::optional_poly<B> z=d; // asserts, because optional_poly should
be constructed with the concrete derived type
// this is the correct way to initialize optional_poly. D must be derived
from B (static assertion otherwise).
boost::optional_poly<B> z=D();
std::cerr<<*z<<"\n"; // this prints 1
boost::optional_poly<B> s; // uninitialized (empty) optional_poly for
interface B
s.swap(z); // now s has a D, while z is empty
std::cerr<<*s<<"\n"; // prints 1
return 0;
}
A bit more complex example:
#include "optional_poly.hpp"
#include <boost/none.hpp>
#include <iostream>
// we will show how simple callable objects can be returned easily and
efficiently from a function (i.e. we implement a really small subset of
boost::function)
struct Callable {
virtual void operator()() const=0;
};
// inheritance is used here. This wrapper can host function pointers or
functors seamlessly, exposing to the outside the Callable interface
template<class T>
struct MakeCallable: Callable {
MakeCallable(const T& t) : val(t) {}
T val;
void operator()() const {
val();
}
};
void my_fun() { std::cout<<"got 1\n"; }
struct MyFunc { void operator()() const { std::cout<<"got 2\n"; } };
// the function returning different Callable objects depending on the value
of the parameter
boost::optional_poly<Callable> foo(int i) {
switch(i) {
case 1:
return MakeCallable<void (*)()>(my_fun);
case 2:
return MakeCallable<MyFunc>(MyFunc());
}
return boost::none;
}
// main shows how foo() is invoked, and its result is used
int main() {
boost::optional_poly<Callable> c;
for(int i=0;i<3; ++i) {
c=foo(i);
if(c) { std::cout<<"Going to call c for "<<i<<" ... ";(*c)(); }
else { std::cout<<"empty c for "<<i<<"\n"; }
}
return 0;
}
Output:
empty c for 0
Going to call c for 1 ... got 1
Going to call c for 2 ... got 2
Wasn't this easy? And this small example did not dynamically allocate any
memory, because the Callable objects are all smaller than the small object
threshold (default value is 128, can be overridden specifying a second
template parameter to optional_poly, e.g. optional_poly<Callable,0> will
always allocate from the heap).
Sounds interesting for being submitted to boost?
Corrado
-- __________________________________________________________________________ dott. Corrado Zoccolo mailto: zoccolo_at_[hidden] PhD - Department of Computer Science - University of Pisa, Italy --------------------------------------------------------------------------
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk