考虑以下代码:
#include <functional>
#include <iostream>
struct S1
{
S1(const std::function<void()>& _func) : func{_func} {}
std::function<void()> func;
};
struct S2
{
template<typename F>
S2(F&& _func) : func{std::forward<F>(_func)} {}
std::function<void()> func;
};
int main()
{
S1 s1{[](){ std::cout << "S1 function called\n"; }};
S2 s2{[](){ std::cout << "S2 function called\n"; }};
s1.func();
s2.func();
return 0;
}
我试图了解这两个构造函数实现的设计优点/缺点是什么。 另外,两者之间是否存在性能差异。
我的主要问题是,当知道可调用对象的签名并将其存储为类中的 std::function 时,是否应该接受 std::function 对象或对可调用对象的通用引用?
据我所知,通用引用是首选方法,因为它为编译器提供了进行内联的最佳机会。
但是,我可以想到使用通用引用的一些缺点:
- 它可能与采用相同数量参数的其他构造函数发生冲突,因此应使用 SFINAE 或 C++20 概念来保护它。 (这是一个库问题,所以没什么大不了的)
- 对于它期望的可调用类型不太明确(也可以使用概念进行约束)。 (这可以认为是用户问题)
- 由于前面的缺点,不适合可调用的情况下的错误点从调用站点移至成员初始化站点,这更难理解。 (这绝对是用户问题)
我想我真正的问题是: 您是否应该在构造函数接口(interface)中公开类的 std::function 性质,或者这是必须保持隐藏的实现细节?
最佳答案
第一个示例将始终复制,即使可以避免,因此它不是最佳的。
第二个示例有您提到的缺点。
推荐的方式是按值获取并移动:
struct S3
{
S3(std::function<void()> _func) : func{std::move(_func)} {}
std::function<void()> func;
};
现在这可能看起来违反直觉,因为您按值获取复制成本可能很高的对象。但这不是问题。我们来分析一下可能的情况:
调用者无法给您可移动的对象。在这种情况下,拷贝是不可避免的。这会导致复制后移动:
auto l = []() {}; S3 s4{l}; l();
调用者给你一个 xvalue。在这种情况下,涉及两个 Action :
auto l = []() {}; S3 s4{std::move(l)}; // no more uses of l
调用者给你一个纯右值。这会导致 1 或 2 次移动。从 C++17 开始,使用新的临时具体化规则,保证您只需一步。
S3 s4{[]() {}};
正如你所看到的,即使你按值取值,你也不会做任何不必要的复制操作,前提是 Action 便宜。
关于c++ - 您应该通过 std::function 还是通过通用引用将可调用对象作为接收器参数传递?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70328394/