在this question上工作的时候,我注意到GCC(v4.7)的std::function
实现在它们被值占用时会移动它的参数。以下代码显示了此行为:
#include <functional>
#include <iostream>
struct CopyableMovable
{
CopyableMovable() { std::cout << "default" << '\n'; }
CopyableMovable(CopyableMovable const &) { std::cout << "copy" << '\n'; }
CopyableMovable(CopyableMovable &&) { std::cout << "move" << '\n'; }
};
void foo(CopyableMovable cm)
{ }
int main()
{
typedef std::function<void(CopyableMovable)> byValue;
byValue fooByValue = foo;
CopyableMovable cm;
fooByValue(cm);
}
// outputs: default copy move move
我们在这里看到cm
的副本被执行(这似乎是合理的,因为byValue
的参数是按值获取的),但是有两个动作。由于function
在cm
的副本上运行,因此它移动其参数的事实可被视为不重要的实现细节。但是,此行为会导致一些问题when using function
together with bind
:
#include <functional>
#include <iostream>
struct MoveTracker
{
bool hasBeenMovedFrom;
MoveTracker()
: hasBeenMovedFrom(false)
{}
MoveTracker(MoveTracker const &)
: hasBeenMovedFrom(false)
{}
MoveTracker(MoveTracker && other)
: hasBeenMovedFrom(false)
{
if (other.hasBeenMovedFrom)
{
std::cout << "already moved!" << '\n';
}
else
{
other.hasBeenMovedFrom = true;
}
}
};
void foo(MoveTracker, MoveTracker) {}
int main()
{
using namespace std::placeholders;
std::function<void(MoveTracker)> func = std::bind(foo, _1, _1);
MoveTracker obj;
func(obj); // prints "already moved!"
}
标准是否允许此行为?允许std::function
移动其参数吗?如果是这样,我们可以将bind
返回的包装器转换为带有值参数的std::function
,这是正常的,即使这会在处理多个占位符时触发意外行为吗?
答案 0 :(得分:17)
std::function
将提供的参数传递给包含std::forward
的函数。例如对于std::function<void(MoveTracker)>
,函数调用运算符等效于
void operator(CopyableMovable a)
{
f(std::forward<CopyableMovable>(a));
}
由于std::forward<T>
在std::move
不是引用类型时相当于T
,因此这会占您第一个示例中的一个动作。第二种情况可能是必须通过std::function
内的间接层。
这也说明了使用std::bind
作为包装函数时遇到的问题:std::bind
也指定转发其参数,在这种情况下它正在通过std::forward
内std::function
调用产生的右值引用。因此,绑定表达式的函数调用运算符将向每个参数转发rvalue引用。不幸的是,由于你重复使用了占位符,它在两种情况下都是对同一个对象的右值引用,因此对于可移动类型,无论先构造哪个都会移动值,第二个参数将得到一个空壳。