我的插槽 class 错过了 std::function 有什么?
What does my slot class miss that std::function has?
我写了自己的 "slot" aka "callable wrapper" 因为我想在其他对象上提供成员函数槽重新绑定(即我需要一种方法来存储成员函数指针和指向 [= =29=] 有问题)。
我运行进行了一次小型测试,发现std::function
在我的系统(64位Linux)上是两倍(GCC/libstdc++)到三倍( Clang/libc++) 大小与我自己实现的类似class,大小为16字节。非成员函数和 lambda 的实现是这样的(const void*
第一个参数是为了与此处未显示的成员函数槽保持一致):
template<typename... ArgTypes>
class slot
{
public:
virtual ~slot() = default;
virtual void operator()(const void* object, ArgTypes...) const = 0;
protected:
slot() = default;
};
template<typename Callable, typename... ArgTypes>
class callable_slot : public slot<ArgTypes...>
{
public:
callable_slot(Callable function_pointer_or_lambda) : callable(function_pointer_or_lambda) {}
virtual void operator()(const void*, ArgTypes... args) const override { callable(args...); }
private:
Callable callable;
};
template<typename Callable>
class callable_slot<Callable> : public slot<>
{
public:
callable_slot(Callable function_pointer_or_lambda) : callable(function_pointer_or_lambda) {}
virtual void operator()(const void*) const override { callable(); }
private:
Callable callable;
};
template<typename Callable, typename... ArgTypes>
using function_slot = callable_slot<Callable, ArgTypes...>;
我知道这里没有实现 target
之类的东西,但我认为任何缺失的功能都不会增加对象的大小。
我要问的是:为什么 std::function
的尺寸比我上面的廉价实现大?
您的 class 功能与 std::function
提供的功能有很大不同。您要求 class 的用户提供 'callable' 对象的实际类型作为模板的参数。
相反,std::function
不需要这个,可以处理任何可调用对象,只要它有operator()
需要的接口.尝试将您的模板与未知类型的对象一起使用,例如 std::bind
的结果,您就会明白我的意思。
由于功能差异很大,比较大小没有实际意义。
您的 function_slot
采用 Callable
和一组 args...
,returns 是从 slot<args...>
继承的类型 virtual operator()
。
要以多态方式将其用作值,您必须将其包装在智能指针中并将其存储在堆上,并且必须转发包装 类 operator()
到 slot<args...>
个。
std::function
对应于 那个包装器 ,而不是你的 slot
或 callable_slot
对象。
template<class...Args>
struct smart_slot {
template<class Callable> // add SFINAE tests here TODO! IMPORTANT!
smart_slot( Callable other ):
my_slot( std::make_unique<callable_slot<Callable, Args...>>( std::move(other) ) )
{}
void operator()( Args...args ) const {
return (*my_slot)(std::forward<Args>(args)...);
}
// etc
private:
std::unique_ptr<slot<Args...>> my_slot;
};
smart_slot
比您的代码更接近 std::function
。就 std::function
而言,您编写的所有内容都是 std::function
用户永远看不到的实现细节。
现在,这只需要 std::function
是一个指针的大小。 std::function
更大,因为它具有所谓的小对象优化。
它不只是存储一个智能指针,它本身还有一块内存。如果您传入的对象适合该内存块,它会就地构造该内存块,而不是进行堆分配。
std::function
基本上是强制执行此操作的简单情况,例如传递函数指针。质量实现适用于更大和更复杂的对象。 MSVC 对大小不超过两个 std::string
的对象执行此操作。
这意味着如果你这样做:
std::function<void(std::ostream&)> hello_world =
[s = "hello world"s](std::ostream& os)
{
os << s;
};
hello_world(std::cout);
它没有对 std::function
的良好实现进行动态分配。
请注意,一些主要的库供应商在这种情况下会进行动态分配。
我写了自己的 "slot" aka "callable wrapper" 因为我想在其他对象上提供成员函数槽重新绑定(即我需要一种方法来存储成员函数指针和指向 [= =29=] 有问题)。
我运行进行了一次小型测试,发现std::function
在我的系统(64位Linux)上是两倍(GCC/libstdc++)到三倍( Clang/libc++) 大小与我自己实现的类似class,大小为16字节。非成员函数和 lambda 的实现是这样的(const void*
第一个参数是为了与此处未显示的成员函数槽保持一致):
template<typename... ArgTypes>
class slot
{
public:
virtual ~slot() = default;
virtual void operator()(const void* object, ArgTypes...) const = 0;
protected:
slot() = default;
};
template<typename Callable, typename... ArgTypes>
class callable_slot : public slot<ArgTypes...>
{
public:
callable_slot(Callable function_pointer_or_lambda) : callable(function_pointer_or_lambda) {}
virtual void operator()(const void*, ArgTypes... args) const override { callable(args...); }
private:
Callable callable;
};
template<typename Callable>
class callable_slot<Callable> : public slot<>
{
public:
callable_slot(Callable function_pointer_or_lambda) : callable(function_pointer_or_lambda) {}
virtual void operator()(const void*) const override { callable(); }
private:
Callable callable;
};
template<typename Callable, typename... ArgTypes>
using function_slot = callable_slot<Callable, ArgTypes...>;
我知道这里没有实现 target
之类的东西,但我认为任何缺失的功能都不会增加对象的大小。
我要问的是:为什么 std::function
的尺寸比我上面的廉价实现大?
您的 class 功能与 std::function
提供的功能有很大不同。您要求 class 的用户提供 'callable' 对象的实际类型作为模板的参数。
相反,std::function
不需要这个,可以处理任何可调用对象,只要它有operator()
需要的接口.尝试将您的模板与未知类型的对象一起使用,例如 std::bind
的结果,您就会明白我的意思。
由于功能差异很大,比较大小没有实际意义。
您的 function_slot
采用 Callable
和一组 args...
,returns 是从 slot<args...>
继承的类型 virtual operator()
。
要以多态方式将其用作值,您必须将其包装在智能指针中并将其存储在堆上,并且必须转发包装 类 operator()
到 slot<args...>
个。
std::function
对应于 那个包装器 ,而不是你的 slot
或 callable_slot
对象。
template<class...Args>
struct smart_slot {
template<class Callable> // add SFINAE tests here TODO! IMPORTANT!
smart_slot( Callable other ):
my_slot( std::make_unique<callable_slot<Callable, Args...>>( std::move(other) ) )
{}
void operator()( Args...args ) const {
return (*my_slot)(std::forward<Args>(args)...);
}
// etc
private:
std::unique_ptr<slot<Args...>> my_slot;
};
smart_slot
比您的代码更接近 std::function
。就 std::function
而言,您编写的所有内容都是 std::function
用户永远看不到的实现细节。
现在,这只需要 std::function
是一个指针的大小。 std::function
更大,因为它具有所谓的小对象优化。
它不只是存储一个智能指针,它本身还有一块内存。如果您传入的对象适合该内存块,它会就地构造该内存块,而不是进行堆分配。
std::function
基本上是强制执行此操作的简单情况,例如传递函数指针。质量实现适用于更大和更复杂的对象。 MSVC 对大小不超过两个 std::string
的对象执行此操作。
这意味着如果你这样做:
std::function<void(std::ostream&)> hello_world =
[s = "hello world"s](std::ostream& os)
{
os << s;
};
hello_world(std::cout);
它没有对 std::function
的良好实现进行动态分配。
请注意,一些主要的库供应商在这种情况下会进行动态分配。