我应该通过const-reference传递std :: function吗?

Sve*_*ing 123 c++ reference function

假设我有一个函数,它需要std::function:

void callFunction(std::function<void()> x)
{
    x();
}
Run Code Online (Sandbox Code Playgroud)

我应该x通过const-reference来代替吗?:

void callFunction(const std::function<void()>& x)
{
    x();
}
Run Code Online (Sandbox Code Playgroud)

这个问题的答案是否会根据函数的作用而改变?例如,如果它是一个类成员函数或构造函数,它存储或初始化std::function成成员变量.

Yak*_*ont 67

如果您想要性能,请在存储时按值传递.

假设您有一个名为"在UI线程中运行此函数"的函数.

std::future<void> run_in_ui_thread( std::function<void()> )
Run Code Online (Sandbox Code Playgroud)

它在"ui"线程中运行一些代码,然后future在完成时发出信号.(在UI框架中很有用,其中UI线程是你应该弄乱UI元素的地方)

我们正在考虑两个签名:

std::future<void> run_in_ui_thread( std::function<void()> ) // (A)
std::future<void> run_in_ui_thread( std::function<void()> const& ) // (B)
Run Code Online (Sandbox Code Playgroud)

现在,我们可能会使用如下:

run_in_ui_thread( [=]{
  // code goes here
} ).wait();
Run Code Online (Sandbox Code Playgroud)

这将创建一个匿名闭包(lambda),构造一个std::function,将其传递给run_in_ui_thread函数,然后等待它在主线程中完成运行.

在情况(A)中,std::function直接由我们的lambda构造,然后在lambda中使用run_in_ui_thread.lambda moved进入std::function,所以任何可移动的状态都有效地进入它.

在第二种情况下,std::function创建一个临时值,将lambda move放入其中,然后std::function通过引用使用临时值run_in_ui_thread.

到目前为止,这么好 - 他们两个表现相同.除了run_in_ui_thread要将其函数参数的副本发送到ui线程执行之外!(它将在完成之前返回,因此它不能仅使用对它的引用).对于情况(A),我们简单movestd::function将其长期储存.在情况(B)中,我们被迫复制std::function.

该商店使价值传递更加优化.如果您有可能存储副本std::function,则按值传递.否则,任何一种方式都大致相同:按值的唯一缺点是,如果你采用相同的体积std::function并且使用一种子方法接着使用它.除此之外,a move和a一样高效const&.

现在,如果我们内部存在持久状态,那么两者之间还存在一些其他差异std::function.

假设std::function存储一些具有a的对象operator() const,但它也有一些mutable它修改的数据成员(多么粗鲁!).

在这种std::function<> const&情况下,mutable修改的数据成员将传播出函数调用.在这种std::function<>情况下,他们不会.

这是一个相对奇怪的角落案例.

你想像std::function任何其他可能重量级,便宜的移动类型一样对待.移动很便宜,复制可能很昂贵.

  • @ceztko 在 (A) 和 (B) 两种情况下,临时 `std::function` 都是从 lambda 创建的。在 (A) 中,临时变量被忽略到 `run_in_ui_thread` 的参数中。在 (B) 中,对所述临时对象的引用被传递给 `run_in_ui_thread`。只要您的 `std::function` 是从 lambdas 创建的临时对象,该子句就成立。上一段处理了 `std::function` 持续存在的情况。如果我们*不*存储,只是从 lambda 创建,则 `function const&amp;` 和 `function` 具有完全相同的开销。 (2认同)
  • @Yakk-AdamNevraumont 是否会更完整地涵盖另一种通过右值参考传递的选项:`std::future&lt;void&gt; run_in_ui_thread( std::function&lt;void()&gt;&amp;&amp; )` (2认同)

Ben*_*igt 31

如果您担心性能,并且没有定义虚拟成员函数,那么您很可能根本不应该使用它std::function.

使仿函数类型成为模板参数允许更大的优化std::function,包括内联仿函数逻辑.这些优化的效果可能大大超过了复制与间接关于如何传递的问题std::function.

快点:

template<typename Functor>
void callFunction(Functor&& x)
{
    x();
}
Run Code Online (Sandbox Code Playgroud)

  • @Ben:我认为实现这个最现代化的嬉皮友好的方式是使用'的std ::向前<函子>(X)();`,以保持函子的价值范畴,因为它是一个"通用"参考.但是,在99%的案例中不会有所作为. (12认同)
  • 实际上我一点也不担心性能。我只是认为在应该使用的地方使用常量引用是常见的做法(想到了字符串和向量)。 (2认同)
  • @arias_JC:如果参数是lambda,则它已经是一个右值。如果有左值,则可以使用`std :: move`,如果不再需要它,或者如果您不想移出现有对象,则可以直接传递。引用折叠规则确保`callFunction &lt;T&&gt;()`的参数类型为T&`,而不是T &amp;&amp;。 (2认同)

sya*_*yam 24

像往常一样在C++ 11中,传递value/reference/const-reference取决于你对你的参数做什么.std::function没有什么不同.

按值传递允许您将参数移动到变量(通常是类的成员变量):

struct Foo {
    Foo(Object o) : m_o(std::move(o)) {}

    Object m_o;
};
Run Code Online (Sandbox Code Playgroud)

当您知道您的函数将移动其参数时,这是最佳解决方案,这样您的用户就可以控制他们调用您的函数的方式:

Foo f1{Object()};               // move the temporary, followed by a move in the constructor
Foo f2{some_object};            // copy the object, followed by a move in the constructor
Foo f3{std::move(some_object)}; // move the object, followed by a move in the constructor
Run Code Online (Sandbox Code Playgroud)

我相信你已经知道(非)const-references的语义,所以我不会说明这一点.如果您需要我添加更多关于此的解释,请询问并且我将更新.