std::function
提供了一个从右值引用构造函数。
根据标准,移动的函数对象会发生什么?它会变成空的,这样再次调用它就没有任何效果吗?
这个问题非常令人困惑。我将尝试清晰地阐述...
本节描述了std定义对象的移动来源状态:
17.6.5.15 [lib.types.movedfrom]
在C++标准库中定义的类型的对象可能被移动(12.8)。移动操作可以明确指定或隐式生成。除非另有规定,这样的移动源自对象将放置在有效但未指定的状态。
这是什么意思?这意味着,给定一个std定义的移动来源对象,您可以对该对象进行任何不需要先验知识的操作。不需要先验知识的当前状态的类别是那些没有前提条件的操作。
例如,您可以在已移动的vector
上调用clear()
,因为vector::clear()
没有前提条件。但是您不能调用pop_back()
,因为它具有前提条件。
具体看function
的调用运算符:
20.8.11.2.4 [func.wrap.func.inv]
R operator()(ArgTypes... args) const
效果: 调用 INVOKE(f, std::forward(args)..., R) (20.8.2),其中 f 是 *this 的目标对象(20.8.1)。
返回值: 如果 R 是 void,则返回 Nothing,否则返回 INVOKE (f, std::forward(args)..., R) 的返回值。
异常: 如果 !*this,则抛出 bad_function_call 异常;否则,抛出包装的可调用对象抛出的异常。
请注意,没有前置条件或要求条款。这意味着调用已被移动的 function
的调用运算符并不是未定义行为。无论 function
处于什么状态,您都不会违反任何先决条件。
请注意,规范从未说过调用将没有任何效果。因此,没有产生效果不是一种可能性。
调用将调用封装的函数或抛出 bad_function_call
异常。这是唯一的两个选择。它的行为取决于 function
对象的状态。而 function
对象的状态是未指定的 ([lib.types.movedfrom])。
std::function
并在析构函数中调用它,如果该函数为空,则会触发std::terminate()
,因为在析构函数中抛出异常会导致这种情况。 因此,情况稍有改变:先抛出 std::bad_function_call
,然后是std::terminate()
。 - user5154412std::function
与移动语义和析构函数上的noexcept一起成为标准(C++11)。 - Howard Hinnantstd::terminate()
的调用。这样说对吗? - user5154412noexcept(false)
。而在后一种情况下,你必须非常小心,否则可能仍然会导致 std::terminate()
。 - Howard Hinnant根据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}};
在将 g
移动构造为 h
后,人们期望包含的 bind
已从 g
转移到了 h
,而不是被复制,因此 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");
}
这并不一定是最优的行为;内联小的可调用对象(例如函数指针)会导致复制可调用对象比移动并将原始对象清空更高效,因此另一种实现可能会使g
保留其非空的可调用状态。
bad_function_call
异常。 - Arne Mertzfunction<...> old;
function<...> new_ = std::move(old);
old = function<...>(...); //Reset to known state.
old(...); //Call is well-defined.
[func.wrap.func.con]:
function(function&& f);
template <class A> function(allocator_arg_t, const A& a, function&& f);