是否允许`std :: function`移动其参数?

时间:2012-04-04 09:33:37

标签: c++ function c++11 bind

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的参数是按值获取的),但是有两个动作。由于functioncm的副本上运行,因此它移动其参数的事实可被视为不重要的实现细节。但是,此行为会导致一些问题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,这是正常的,即使这会在处理多个占位符时触发意外行为吗?

1 个答案:

答案 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::forwardstd::function调用产生的右值引用。因此,绑定表达式的函数调用运算符将​​向每个参数转发rvalue引用。不幸的是,由于你重复使用了占位符,它在两种情况下都是对同一个对象的右值引用,因此对于可移动类型,无论先构造哪个都会移动值,第二个参数将得到一个空壳。