La réponse de @David Rodríguez - dribeas est bonne pour démontrer l'effacement de type mais pas assez car l'effacement de type inclut également la manière dont les types sont copiés (dans cette réponse, l'objet fonction ne sera pas constructible par copie). Ces comportements sont également stockés dans lefunction
objet, en plus des données du foncteur.
L'astuce, utilisée dans l'implémentation STL d'Ubuntu 14.04 gcc 4.8, consiste à écrire une fonction générique, à la spécialiser avec chaque type de foncteur possible et à les convertir en un type de pointeur de fonction universel. Par conséquent, les informations de type sont effacées .
J'ai bricolé une version simplifiée de cela. J'espère que ça aidera
#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;
}
Il y a aussi quelques optimisations dans la version STL
- le
construct_f
etdestroy_f
sont mélangés en un pointeur de fonction (avec un paramètre supplémentaire qui dit quoi faire) comme pour sauver quelques octets
- les pointeurs bruts sont utilisés pour stocker l'objet foncteur, avec un pointeur de fonction dans un
union
, de sorte que lorsqu'un function
objet est construit à partir d'un pointeur de fonction, il sera stocké directement dans l' union
espace plutôt que dans l' espace du tas
Peut-être que l'implémentation STL n'est pas la meilleure solution car j'ai entendu parler d'une implémentation plus rapide . Cependant, je pense que le mécanisme sous-jacent est le même.
std::function
quelque temps. Il s'agit essentiellement d'une classe de descripteurs pour un objet polymorphe. Une classe dérivée de la classe de base interne est créée pour contenir les paramètres, alloués sur le tas - puis le pointeur vers celui-ci est conservé comme un sous-objet destd::function
. Je crois qu'il utilise le comptage de références commestd::shared_ptr
pour gérer la copie et le déplacement.