Câu trả lời từ @David Rodríguez - dribeas là tốt để chứng minh kiểu xóa nhưng chưa đủ tốt vì xóa kiểu cũng bao gồm cách các kiểu được sao chép (trong câu trả lời đó, đối tượng hàm sẽ không được sao chép-tạo). Những hành vi đó cũng được lưu trữ trong function
đối tượng, bên cạnh dữ liệu functor.
Thủ thuật, được sử dụng trong triển khai STL từ Ubuntu 14.04 gcc 4.8, là viết một hàm chung, chuyên biệt hóa nó với từng loại bộ chức năng có thể và chuyển chúng thành một loại con trỏ hàm phổ quát. Do đó thông tin loại bị xóa .
Tôi đã soạn thảo một phiên bản đơn giản của điều đó. Hy vọng nó sẽ giúp
#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;
}
Ngoài ra còn có một số tối ưu hóa trong phiên bản STL
- các
construct_f
và destroy_f
được trộn vào một con trỏ hàm (với một tham số bổ sung mà nói làm gì) như để tiết kiệm một số byte
- con trỏ thô được sử dụng để lưu trữ đối tượng functor, cùng với một con trỏ hàm trong a
union
, để khi một function
đối tượng được xây dựng từ một con trỏ hàm, nó sẽ được lưu trữ trực tiếp trong union
không gian heap thay vì
Có thể việc triển khai STL không phải là giải pháp tốt nhất như tôi đã nghe về một số cách triển khai nhanh hơn . Tuy nhiên tôi tin rằng cơ chế cơ bản là như nhau.
std::function
một thời gian trước. Về cơ bản nó là một lớp xử lý cho một đối tượng đa hình. Một lớp dẫn xuất của lớp cơ sở bên trong được tạo ra để chứa các tham số, được phân bổ trên heap - sau đó con trỏ tới lớp này được giữ như một đối tượng subobject củastd::function
. Tôi tin rằng nó sử dụng cách đếm tham chiếustd::shared_ptr
để xử lý việc sao chép và di chuyển.