La risposta di @David Rodríguez - dribeas è utile per dimostrare la cancellazione del tipo ma non abbastanza buona poiché la cancellazione del tipo include anche il modo in cui i tipi vengono copiati (in quella risposta l'oggetto funzione non sarà costruibile dalla copia). Questi comportamenti sono anche memorizzati function
nell'oggetto, oltre ai dati del funtore.
Il trucco, utilizzato nell'implementazione STL da Ubuntu 14.04 gcc 4.8, è scrivere una funzione generica, specializzarla con ogni possibile tipo di funtore e lanciarli in un tipo di puntatore a funzione universale. Pertanto le informazioni sul tipo vengono cancellate .
Ho messo a punto una versione semplificata di questo. Spero che ti aiuti
#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;
}
Ci sono anche alcune ottimizzazioni nella versione STL
- la
construct_f
e destroy_f
si mescolano in un puntatore a funzione (con un parametro aggiuntivo che dice cosa fare) per salvare alcuni byte
- i puntatori non elaborati vengono utilizzati per memorizzare l'oggetto funtore, insieme a un puntatore a funzione in a
union
, in modo che quando un function
oggetto viene costruito da un puntatore a funzione, verrà memorizzato direttamente nello union
spazio anziché nell'heap
Forse l'implementazione STL non è la soluzione migliore poiché ho sentito parlare di un'implementazione più rapida . Tuttavia credo che il meccanismo sottostante sia lo stesso.
std::function
qualche tempo fa. È essenzialmente una classe handle per un oggetto polimorfico. Viene creata una classe derivata della classe base interna per contenere i parametri, allocati sull'heap, quindi il puntatore a questo viene mantenuto come un oggetto secondario distd::function
. Credo che utilizzi il conteggio dei riferimentistd::shared_ptr
per gestire la copia e lo spostamento.