【发布时间】:2012-04-18 00:09:33
【问题描述】:
在处理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 是否正常,即使这在处理多次出现的占位符时会触发意外行为?
【问题讨论】:
-
在我看来,占位符的问题比
std::function更多。即,在创建tie时,将使用从原始参数移动到两个预期输出的事实。 -
有趣的是,Visual C++ 11 编译器在第一个示例中打印“默认复制移动”,而不打印“已经移动!”在第二。我想知道这个额外的动作是否可能来自 std::function 的内部工作和/或完美转发。
-
@MatthieuM。你能详细说明一下吗?我对占位符的实现不是很熟悉。如果问题来自占位符,那么使用
auto推导出“bind-wrapper”类型,而不是使用std::function,怎么没有出现问题? -
@LucDanton 在后一种情况下,函数对象(包装器)的参数将是
MoveTracker&类型的左值引用,并将作为对foo的左值引用转发两次,结果是在这些左值引用的两个副本构造中。