@David Rodríguez-dribeas의 답변은 유형 삭제를 시연하는 데 좋지만 유형 삭제에는 유형이 복사되는 방법도 포함되기 때문에 충분하지 않습니다 (그 대답에서 함수 객체는 복사 구성이 불가능합니다). 이러한 행동은 function펑터 데이터 외에 객체 에도 저장됩니다 .
Ubuntu 14.04 gcc 4.8의 STL 구현에 사용되는 트릭은 하나의 일반 함수를 작성하고 가능한 각 펑터 유형으로 특수화하고 범용 함수 포인터 유형으로 캐스팅하는 것입니다. 따라서 유형 정보가 지워 집니다.
나는 그것의 단순화 된 버전을 만들었습니다. 도움이되기를 바랍니다.
#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일부 바이트를 저장할 수로 (무엇을 알려주는 추가 매개 변수를 사용하여) 하나의 함수 포인터로 혼합
- 원시 포인터는 함수 포인터와 함께 functor 객체를 저장하는 데 사용
union되므로 function객체가 함수 포인터에서 생성 될 때 union힙 공간 이 아닌 직접 저장됩니다.
더 빠른 구현 에 대해 들었던 것처럼 STL 구현이 최상의 솔루션이 아닐 수도 있습니다 . 그러나 기본 메커니즘은 동일하다고 생각합니다.
std::function한동안 gcc / stdlib 구현을 살펴 보았습니다 . 본질적으로 다형성 객체에 대한 핸들 클래스입니다. 내부 기본 클래스의 파생 클래스는 힙에 할당 된 매개 변수를 보유하기 위해 만들어지며 이에 대한 포인터는의 하위 개체로 유지됩니다std::function. 나는 그것이std::shared_ptr복사 및 이동을 처리하는 것과 같이 참조 계수를 사용한다고 생각합니다 .