Ответ от @David Rodríguez - dribeas хорош для демонстрации стирания типа, но недостаточно хорош, поскольку стирание типа также включает в себя то, как типы копируются (в этом ответе объект функции не будет копируемым). Эти поведения также сохраняются в function
объекте, помимо данных функтора.
Уловка, используемая в реализации STL из Ubuntu 14.04 gcc 4.8, заключается в том, чтобы написать одну общую функцию, специализовать ее для каждого возможного типа функтора и привести их к универсальному типу указателя функции. Поэтому информация о типе стирается .
Я придумал упрощенную версию этого. Надеюсь, это поможет
#include <iostream>
#include <memory>
template <typename T>
class function;
template <typename R, typename... Args>
class function<R(Args...)>
{
// function pointer types for the type-erasure behaviors
// all these char* parameters are actually casted from some functor type
typedef R (*invoke_fn_t)(char*, Args&&...);
typedef void (*construct_fn_t)(char*, char*);
typedef void (*destroy_fn_t)(char*);
// type-aware generic functions for invoking
// the specialization of these functions won't be capable with
// the above function pointer types, so we need some cast
template <typename Functor>
static R invoke_fn(Functor* fn, Args&&... args)
{
return (*fn)(std::forward<Args>(args)...);
}
template <typename Functor>
static void construct_fn(Functor* construct_dst, Functor* construct_src)
{
// the functor type must be copy-constructible
new (construct_dst) Functor(*construct_src);
}
template <typename Functor>
static void destroy_fn(Functor* f)
{
f->~Functor();
}
// these pointers are storing behaviors
invoke_fn_t invoke_f;
construct_fn_t construct_f;
destroy_fn_t destroy_f;
// erase the type of any functor and store it into a char*
// so the storage size should be obtained as well
std::unique_ptr<char[]> data_ptr;
size_t data_size;
public:
function()
: invoke_f(nullptr)
, construct_f(nullptr)
, destroy_f(nullptr)
, data_ptr(nullptr)
, data_size(0)
{}
// construct from any functor type
template <typename Functor>
function(Functor f)
// specialize functions and erase their type info by casting
: invoke_f(reinterpret_cast<invoke_fn_t>(invoke_fn<Functor>))
, construct_f(reinterpret_cast<construct_fn_t>(construct_fn<Functor>))
, destroy_f(reinterpret_cast<destroy_fn_t>(destroy_fn<Functor>))
, data_ptr(new char[sizeof(Functor)])
, data_size(sizeof(Functor))
{
// copy the functor to internal storage
this->construct_f(this->data_ptr.get(), reinterpret_cast<char*>(&f));
}
// copy constructor
function(function const& rhs)
: invoke_f(rhs.invoke_f)
, construct_f(rhs.construct_f)
, destroy_f(rhs.destroy_f)
, data_size(rhs.data_size)
{
if (this->invoke_f) {
// when the source is not a null function, copy its internal functor
this->data_ptr.reset(new char[this->data_size]);
this->construct_f(this->data_ptr.get(), rhs.data_ptr.get());
}
}
~function()
{
if (data_ptr != nullptr) {
this->destroy_f(this->data_ptr.get());
}
}
// other constructors, from nullptr, from function pointers
R operator()(Args&&... args)
{
return this->invoke_f(this->data_ptr.get(), std::forward<Args>(args)...);
}
};
// examples
int main()
{
int i = 0;
auto fn = [i](std::string const& s) mutable
{
std::cout << ++i << ". " << s << std::endl;
};
fn("first"); // 1. first
fn("second"); // 2. second
// construct from lambda
::function<void(std::string const&)> f(fn);
f("third"); // 3. third
// copy from another function
::function<void(std::string const&)> g(f);
f("forth - f"); // 4. forth - f
g("forth - g"); // 4. forth - g
// capture and copy non-trivial types like std::string
std::string x("xxxx");
::function<void()> h([x]() { std::cout << x << std::endl; });
h();
::function<void()> k(h);
k();
return 0;
}
В версии STL также есть некоторые оптимизации.
construct_f
и destroy_f
смешиваются в один указатель на функцию (с дополнительным параметром , который говорит , что делать), чтобы сохранить несколько байт
- необработанные указатели используются для хранения объекта-функтора вместе с указателем на функцию в a
union
, так что когда function
объект создается из указателя функции, он будет храниться непосредственно в union
пространстве, а не в куче
Возможно, реализация STL - не лучшее решение, поскольку я слышал о более быстрой реализации . Однако я считаю, что основной механизм такой же.
std::function
время назад я изучал реализацию gcc / stdlib . По сути, это класс дескриптора для полиморфного объекта. Производный класс внутреннего базового класса создается для хранения параметров, выделенных в куче - тогда указатель на него сохраняется как подобъектstd::function
. Я считаю, что он использует подсчет ссылок, какstd::shared_ptr
при копировании и перемещении.