This article is about how std::function
is implemented and provide some implements that compiled in pre-C++11.
The std::function
in C++11 is very fantastic as in a static compiling language like C++ it provides a set of interfaces to wrap any kind of callable objects. A more fantastic fact is that the only C++11 feature that std::function
involves is variadic template.
So if we implements a simplified version of std::function
with an arbitrary number of template parameters (say, 3 parameters, 1 for the return type and 2 for parameter types), it could be done in pre-C++11 so we don't have to learn the C++11 features right now.
Let us get down to the implements.
0. One pointer version
The std::function
could be implemented as a class
with only one pointer as its only data member, and of course several virtual functions.
The key point is to declare a virtual base class which could be used to wrap any kinds of callable object, like this.
template <typename Ret, typename Arg0, typename Arg1>
class function<Ret(Arg0, Arg1)> {
// the virtual base class for callables
struct callable_base {
virtual Ret operator()(Arg0 arg0, Arg1 arg1) = 0;
virtual ~callable_base() {}
};
callable_base* callable_ptr;
public:
Ret operator()(Arg0 arg0, Arg1 arg1)
{
// a call to the function is routed to the member pointer.
return (*callable_ptr)(arg0, arg1);
}
private:
// here is the sub-template-class that inherits callable_base
template <typename F>
struct callable
: callable_base
{
// it can store and make use of any callable object since it is a template class
F functor;
callable(F functor)
: functor(functor)
{}
virtual Ret operator()(Arg0 arg0, Arg1 arg1)
{
return functor(arg0, arg1);
}
};
public:
// so that create a 'function' instance is to specialize the 'callable' template with the type of callable object
template <typename F>
function(F f)
: callable_ptr(new callable<F>(f))
{}
};
The implements above is fine to be instanciated and be called, but apparently there are resource leak problems since there is new
in the constructor but no delete
in the destructor.
Before we implements the destructor we must be aware of that copy-constructor and copy-assign operator overload shall be implements along with the destructor.
So let's consider about them.
The basic idea to create copy-constructor is to copy each members recursively. However the problem we have here is we don't know how to copy an abstract base pointer. To solve this, we can add a "copy-constructor" as a virtual function and implement it in the subclass.
template <typename Ret, typename Arg0, typename Arg1>
class function<Ret(Arg0, Arg1)> {
// ...
struct callable_base {
virtual Ret operator()(Arg0 arg0, Arg1 arg1) = 0;
// add a clone virtual function
virtual callable_base* clone() const = 0;
virtual ~callable_base() {}
};
template <typename F>
struct callable
: callable_base
{
F functor;
// ...
// subclass implements it to make a copy of itself
virtual callable_base* clone() const
{
return new callable<F>(functor);
}
};
callable_base* callable_ptr;
public:
// use clone to copy a function object
function(function const& rhs)
: callable_ptr(rhs.callable_ptr->clone())
{}
function& operator=(function const& rhs)
{
delete callable_ptr;
callable_ptr = rhs.callable_ptr->clone();
}
// and delete the pointer when destructed
~function()
{
delete callable_ptr;
}
This is a oversimplification though it could run. The real world implements (like the implement shipped with gcc) uses more tricks to avoid heap allocation in some situation. Let me explain it in the next post.