Type Erasure in C++: How Boost::Shared_Ptr and Boost::Function Work

Type erasure in C++: how boost::shared_ptr and boost::function work?

The idea is simple, you define a base class that has an interface with the functionality you need, and then inherit from it. Since the type erased class uses only that interface, the actual type underneath is forgotten and erased. Alternatively, if the only needed interface can be expressed as free functions you can store pointers to the free functions.

namespace detail {
struct deleter_base {
virtual ~deleter_base() {}
virtual void operator()( void* ) = 0;
};
template <typename T>
struct deleter : deleter_base {
virtual void operator()( void* p ) {
delete static_cast<T*>(p);
}
};
}
template <typename T>
class simple_ptr {
T* ptr;
detail::deleter_base* deleter;
public:
template <typename U>
simple_ptr( U* p ) {
ptr = p;
deleter = new detail::deleter<U>();
}
~simple_ptr() {
(*deleter)( ptr );
delete deleter;
}
};

This is a really simplified smart pointer, but the idea is there. In the particular case of shared_ptr, the deleter is stored as part of the reference count object, that is held by pointer.

practice and discovery of Boost Type Erasure

Type Erasure is useful in an extraordinary amount of situations, to the point where it may actually be thought of as a fundamentally missing language feature that bridges generic and object oriented programming styles.

When we define a class in C++, what we are really defining is both a very specific type and a very specific interface, and that these two things do not necessarily need to be related. A type deals with the data, where as the interface deals with transformations on that data. Generic code, such as in the STL, doesn't care about type, it cares about interface: you can sort anything container or container-like sequence using std::sort, as long as it provides comparison and iterator interface.

Unfortunately, generic code in C++ requires compile time polymorphism: templates. This doesn't help with things which cannot be known until runtime, or things which require a uniform interface.

A simple example is this: how do you store a number of different types in a single container? The simplest mechanism would be to store all of the types in a void*, perhaps with some type information to distinguish them. Another way is to recognize all of these types have the same interface: retrieval. If we could make a single interface for retrieval, then specialize it for each type, then it would be as if part of the type had been erased.

any_iterator is another very useful reason to do this: if you need to iterate over a number of different containers with the same interface, you will need to erase the type of the container out of the type of the iterator. boost::any_range is a subtle enhancement of this, extending it from iterators to ranges, but the basic idea is the same.

In short, any time you need to go from multiple types with a similar interface to a single type with a single interface, you will need some form of type erasure. It is the runtime technique that equates compile time templates.

Doing type erasure safely without boost and c++0x

The only answer to this question is "Roll your own class that already exists in Boost", whether you like ptr_vector, shared_ptr, any, etc. They already have all the bases covered in this regard. Pick your favourite and roll your own implementation, then use that.

Edit: A commenter mentioned TR1. Good shout. TR1 has shared_ptr in it.

boost: serialization of shared_ptr with custom deleter

The only other ugly approach that I can think of is to assume that all deleters are derived from a common base class

This is exactly it. This is the only way to do it, given the way you framed the question.

However, you can of course transpose the design, and make the deleter fixed. The differences in behaviour could be driven from the element_type member data.

So, if you make the element type contain the non-erased deleter, you can

  1. simply include the serialization thereof as part of the object
  2. write an "actual" deleter to relay to it, e.g.:

    template <typename T>
    struct InvertedDeleter {
    void operator()(T* element) const {
    element->get_deleter()(element);
    }
    };

C++ / Boost shared resource class for managing resource lifecycle like shared_ptr

Are you aware that std::shared_ptr can take a custom deleter class? This need not actually use "delete" or "free", but could easily use some other sort of mechanism (such as a reference counting mechanism's release and so on).

Here's a dead simple example for you:

std::shared_ptr<FILE> foo(fopen("la", "r"), fclose);

The deleter just needs to be a function that takes the pointer type that the shared_ptr wraps. In this case, whenfoo goes out of scope, shared_ptr will close the file for you. (Note: this isn't a totally sensible implementation, because no error values are checked. It is just an example).

Type erasure for methods with differing in return types

Type erasure can and has been implemented in C++ in different contexts. The most common approach, which is used in boost::any, std::function< signature >, std::thread and others is based on a non-polymorphic class that is the type erased object, which contains a pointer to an interface type. Internally, during construction, assignment or whenever the user type is erased, an implementation of the interface is instantiated and stored.

As a motivating simplified example, consider that we wanted to create a printable type that can be used to print any type that implements operator<< to std::cout using type erasure. For that we need the type printable, the internal interface printable_impl_base, and the actual implementations:

// regular polymorphic hierarchy:
struct printable_impl_base {
virtual ~printable_impl_base() {}
virtual void print() const = 0;
};
template <typename T>
struct printable_impl : printable_impl_base {
T copy_to_print;
printable_impl( T const & o ) : copy_to_print( o ) {}
virtual void print() const {
std::cout << copy_to_print << std::endl;
}
};

// type erasure is performed in printable:
class printable {
std::shared_ptr<printablable_impl_base> p;
public:
template <typename T>
printable( T obj ) : p( new printable_impl<T>(obj) ) {}
void print() const {
p->print();
}
};

Note that the pattern is very similar to a regular polymorphic hierarchy, with the difference that an interface object is added that is a value type (borrowing the term value type from C#), that holds the actual polymorphic objects inside.

Looking at it this way, it seems kind of simplistic and useless, but that is the fuel that drives boost::any (the internal interface is only a typeid), std::function< void () > (the internal interface is that it implements void operator()), or shared_ptr<> (the interface is the deleter method, that relinquishes the resource).

There is one specific different type of type erasure when the only thing that needs to be done with the type that implements type erasure is to destroy it: use a temporary and bind it to a constant reference... But this is very specific, if you want you can read about it here: http://drdobbs.com/cpp/184403758

In the specific case that you are talking about in the question it is a bit more complex, because you don't want to erase a single type, but rather a couple of them. The Iterable interface must erase the type of the container that it internally holds, and in doing so it has to provide it's own iterators that have to perform type erasure on the iterators from the container. Still, the idea is basically the same, just more work to do to implement.

Building boost::options from a string/boost::any map

boost::any is not applicable to your problem. It performs the most basic form of type erasure: storage and (type-safe) retrieval, and that's it. As you've seen, no other operations can be performed. As jhasse points out, you could just test every type you want to support, but this is a maintenance nightmare.

Better would be to expand upon the idea boost::any uses. Unfortunately this requires a bit of boiler-plate code. If you'd like to try it, there's a new Boost library being discussed right now on the mailing list (titled "[boost] RFC: type erasure") that is essentially a generalized type erasure utility: you define the operations you'd like your erased type to support, and it generates the proper utility type. (It can simulate boost::any, for example, by requiring the erased type be copy-constructible and type-safe, and can simulate boost::function<> by additionally requiring the type be callable.)

Aside from that, though, your best option is probably to write such a type yourself. I'll do it for you:

#include <boost/program_options.hpp>
#include <typeinfo>
#include <stdexcept>

namespace po = boost::program_options;

class any_option
{
public:
any_option() :
mContent(0) // no content
{}

template <typename T>
any_option(const T& value) :
mContent(new holder<T>(value))
{
// above is where the erasure happens,
// holder<T> inherits from our non-template
// base class, which will make virtual calls
// to the actual implementation; see below
}

any_option(const any_option& other) :
mContent(other.empty() ? 0 : other.mContent->clone())
{
// note we need an explicit clone method to copy,
// since with an erased type it's impossible
}

any_option& operator=(any_option other)
{
// copy-and-swap idiom is short and sweet
swap(*this, other);

return *this;
}

~any_option()
{
// delete our content when we're done
delete mContent;
}

bool empty() const
{
return !mContent;
}

friend void swap(any_option& first, any_option& second)
{
std::swap(first.mContent, second.mContent);
}

// now we define the interface we'd like to support through erasure:

// getting the data out if we know the type will be useful,
// just like boost::any. (defined as friend free-function)
template <typename T>
friend T* any_option_cast(any_option*);

// and the ability to query the type
const std::type_info& type() const
{
return mContent->type(); // call actual function
}

// we also want to be able to call options_description::add_option(),
// so we add a function that will do so (through a virtual call)
void add_option(po::options_description desc, const char* name)
{
mContent->add_option(desc, name); // call actual function
}

private:
// done with the interface, now we define the non-template base class,
// which has virtual functions where we need type-erased functionality
class placeholder
{
public:
virtual ~placeholder()
{
// allow deletion through base with virtual destructor
}

// the interface needed to support any_option operations:

// need to be able to clone the stored value
virtual placeholder* clone() const = 0;

// need to be able to test the stored type, for safe casts
virtual const std::type_info& type() const = 0;

// and need to be able to perform add_option with type info
virtual void add_option(po::options_description desc,
const char* name) = 0;
};

// and the template derived class, which will support the interface
template <typename T>
class holder : public placeholder
{
public:
holder(const T& value) :
mValue(value)
{}

// implement the required interface:
placeholder* clone() const
{
return new holder<T>(mValue);
}

const std::type_info& type() const
{
return typeid(mValue);
}

void add_option(po::options_description desc, const char* name)
{
desc.add_options()(name, po::value<T>(), "");
}

// finally, we have a direct value accessor
T& value()
{
return mValue;
}

private:
T mValue;

// noncopyable, use cloning interface
holder(const holder&);
holder& operator=(const holder&);
};

// finally, we store a pointer to the base class
placeholder* mContent;
};

class bad_any_option_cast :
public std::bad_cast
{
public:
const char* what() const throw()
{
return "bad_any_option_cast: failed conversion";
}
};

template <typename T>
T* any_option_cast(any_option* anyOption)
{
typedef any_option::holder<T> holder;

return anyOption.type() == typeid(T) ?
&static_cast<holder*>(anyOption.mContent)->value() : 0;
}

template <typename T>
const T* any_option_cast(const any_option* anyOption)
{
// none of the operations in non-const any_option_cast
// are mutating, so this is safe and simple (constness
// is restored to the return value automatically)
return any_option_cast<T>(const_cast<any_option*>(anyOption));
}

template <typename T>
T& any_option_cast(any_option& anyOption)
{
T* result = any_option_cast(&anyOption);
if (!result)
throw bad_any_option_cast();

return *result;
}

template <typename T>
const T& any_option_cast(const any_option& anyOption)
{
return any_option_cast<T>(const_cast<any_option&>(anyOption));
}

// NOTE: My casting operator has slightly different use than
// that of boost::any. Namely, it automatically returns a reference
// to the stored value, so you don't need to (and cannot) specify it.
// If you liked the old way, feel free to peek into their source.

#include <boost/foreach.hpp>
#include <map>

int main()
{
// (it's a good exercise to step through this with
// a debugger to see how it all comes together)
typedef std::map<std::string, any_option> map_type;
typedef map_type::value_type pair_type;

map_type m;

m.insert(std::make_pair("int", any_option(5)));
m.insert(std::make_pair("double", any_option(3.14)));

po::options_description desc;

BOOST_FOREACH(pair_type& pair, m)
{
pair.second.add_option(desc, pair.first.c_str());
}

// etc.
}

Let me know if something is unclear. :)



Related Topics



Leave a reply



Submit