Die Antwort von @David Rodríguez - dribeas ist gut, um die Typlöschung zu demonstrieren, aber nicht gut genug, da die Typlöschung auch beinhaltet, wie Typen kopiert werden (in dieser Antwort ist das Funktionsobjekt nicht kopierkonstruierbar). Diese Verhaltensweisen werden auch in der gespeichertfunction
neben den Funktordaten Objekt gespeichert.
Der Trick, der in der STL-Implementierung von Ubuntu 14.04 gcc 4.8 verwendet wird, besteht darin, eine generische Funktion zu schreiben, sie auf jeden möglichen Funktionstyp zu spezialisieren und sie in einen universellen Funktionszeigertyp umzuwandeln. Daher werden die Typinformationen gelöscht .
Ich habe eine vereinfachte Version davon zusammengeschustert. Hoffe es wird helfen
#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;
}
Es gibt auch einige Optimierungen in der STL-Version
- das
construct_f
unddestroy_f
werden in einen Funktionszeiger gemischt (mit einem zusätzlichen Parameter, der angibt, was zu tun ist), um einige Bytes zu sparen
- Rohzeiger werden verwendet, um das Funktorobjekt zusammen mit einem Funktionszeiger in a zu speichern
union
, sodass ein function
Objekt , wenn es aus einem Funktionszeiger erstellt wird, direkt im union
Heap-Bereich und nicht im Heap-Bereich gespeichert wird
Vielleicht ist die STL-Implementierung nicht die beste Lösung, da ich von einer schnelleren Implementierung gehört habe . Ich glaube jedoch, dass der zugrunde liegende Mechanismus der gleiche ist.
std::function
. Es ist im Wesentlichen eine Handle-Klasse für ein polymorphes Objekt. Eine abgeleitete Klasse der internen Basisklasse wird erstellt, um die auf dem Heap zugewiesenen Parameter zu speichern. Anschließend wird der Zeiger darauf als Unterobjekt von gespeichertstd::function
. Ich glaube, es verwendet Referenzzählungstd::shared_ptr
, um das Kopieren und Verschieben zu handhaben.