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

Luc*_*lle 17 c++ bind function c++11

在处理这个问题的过程中,我注意到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
Run Code Online (Sandbox Code Playgroud)

我们在这里看到cm执行的副本(这似乎是合理的,因为byValue's参数是按值获取的),但是有两个动作.由于它function是在副本上运行cm,它移动其参数的事实可以被视为一个不重要的实现细节.但是,以下一起使用functionbind,此行为会导致一些问题:

#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!"
}
Run Code Online (Sandbox Code Playgroud)

标准是否允许这种行为?被std::function允许移动其论点?如果是这样,我们可以将返回的包装器bind转换为std::functionwith-value参数,这是正常的,即使在处理多个占位符时会触发意外行为吗?

Ant*_*ams 17

std::function指定将提供的参数传递给包装函数std::forward.例如std::function<void(MoveTracker)>,函数调用运算符相当于

void operator(CopyableMovable a)
{
    f(std::forward<CopyableMovable>(a));
}
Run Code Online (Sandbox Code Playgroud)

由于std::forward<T>相当于std::movewhen T不是引用类型,因此在第一个示例中考虑了其中一个移动.第二种可能是必须通过内部的间接层std::function.

然后,这也说明了你在使用遇到的问题std::bind作为包装的函数:std::bind指定传送其参数,在此情况下,它被传递从产生的右值引用std::forward内部通话std::function.因此,绑定表达式的函数调用运算符将​​向每个参数转发rvalue引用.不幸的是,由于你重复使用占位符,它在两种情况下都是对同一个对象的右值引用,因此对于可移动类型,无论哪个构造首先移动值,第二个参数将得到一个空壳.