使用std :: function移动语义

Mar*_*tin 14 c++ standards c++11

std::function提供了一个rvalue引用的构造函数.按标准移动的函数对象会发生什么?它是否为空,以便再次调用它没有效果?

How*_*ant 19

围绕这个问题存在太多混淆.我打算清楚地说清楚......

本节介绍std定义对象的移动状态:

17.6.5.15 [lib.types.movedfrom]

可以从(12.8)移动C++标准库中定义的类型的对象.可以显式指定或隐式生成移动操作.除非另有规定,否则此类移动物体应置于有效但未指定的状态.

这是什么意思?这意味着给定一个std定义的移动对象,您可以对该对象执行任何操作,而该对象不需要该对象状态的先验知识.不需要事先了解当前状态的行动类别是没有先决条件的行动.

例如,您可以调用clear()移动,vector因为没有先决条件vector::clear().但你不能打电话,pop_back()因为那确实有先决条件.

专门研究以下的呼叫运营商function:

20.8.11.2.4 [func.wrap.func.inv]

R operator()(ArgTypes... args) const
Run Code Online (Sandbox Code Playgroud)

效果:INVOKE(f,std :: forward(args)...,R)(20.8.2),其中f是*this的目标对象(20.8.1).

返回:如果R为空则无效,否则返回值为INVOKE(f,std :: forward(args)...,R).

抛出:bad_function_call if!*this; 否则,包装的可调用对象抛出的任何异常.

请注意,没有前提条件或Requires子句.这意味着,在调用的调用运营商function移动后,从function没有未定义的行为.无论处于何种状态function,您都不会违反此通话的任何先决条件.

请注意,规范在任何情况下都不会说该调用无效.所以没有效果是不可能的.

调用将调用包装函数,或者抛出一个bad_function_call.这是唯一的两个选择.它的行为取决于function对象的状态.并且function未指定对象的状态([lib.types.movedfrom]).


eca*_*mur 15

在20.8.11.2.1p6,function(function &&f)f处于有效状态与一个未确定的值.

空状态是一个有效状态,因此您应该期望从函数对象移动可以为空.

因为function执行类型擦除,并且函数对象可以任意昂贵,所以将从对象移动的优化是有意义的:

std::function<void()> g{std::bind{f, std::array<int, 1000>{}}};
std::function<void()> h{std::move{g}};
Run Code Online (Sandbox Code Playgroud)

h通过移动构建之后g,人们会期望所包含的内容bind已经转移gh而不是复制,因此g将留空.

对于以下程序,gcc 4.5.1打印empty:

#include <functional>
#include <iostream>
void f() {}
int main() {
    std::function<void()> g{f}, h{std::move(g)};
    std::cout << (g ? "not empty\n" : "empty\n");
}
Run Code Online (Sandbox Code Playgroud)

这不一定是最理想的行为; 内联小型callables(例如函数指针)会产生一种情况,即复制callable比移动它并清空移动的对象更有效,因此另一个实现可能会留g在非空的可调用状态.

  • @Martin:"未指定"表示*未指定*.你不知道,所以你不能认为它会. (4认同)

Nic*_*las 7

按标准移动的函数对象会发生什么?

它将处于有效状态(因此可以使用该对象),但它所处的实际状态是未指定的.最后一部分意味着调用任何需要对象处于特定状态的函数不一定有效.

它是否为空,以便再次调用它没有效果?

不能假设它会.调用该函数需要它实际上具有调用函数.这是该州的一部分.由于状态未指定,因此调用它的结果未指定.

如果您想以某种有意义的方式再次使用该对象,只需创建一个新对象function并将其分配给它:

function<...> old;
function<...> new_ = std::move(old);
old = function<...>(...); //Reset to known state.
old(...); //Call is well-defined.
Run Code Online (Sandbox Code Playgroud)