19

在处理这个问题时,我注意到 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,因此它移动其参数的事实可以被视为一个不重要的实现细节。但是,当function与 一起使用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允许移动它的论点?如果是这样,我们可以将返回的包装器转换为按值参数的包装器是否正常bindstd::function即使这在处理多次出现的占位符时会触发意外行为?

4

1 回答 1

18

std::function指定将提供的参数传递给带有std::forward. 例如对于std::function<void(MoveTracker)>,函数调用运算符等价于

void operator(CopyableMovable a)
{
    f(std::forward<CopyableMovable>(a));
}

由于std::forward<T>等效于std::movewhenT不是引用类型,因此这说明了您的第一个示例中的一个动作。第二个可能来自必须通过内部的间接层std::function

std::bind这也解释了您在使用包装函数时遇到的问题:std::bind指定为转发其参数,在这种情况下,它被传递一个由std::forward内部调用产生的右值引用std::function。因此,绑定表达式的函数调用运算符将​​右值引用转发给每个参数。不幸的是,由于您重用了占位符,因此在两种情况下它都是对同一对象的右值引用,因此对于可移动类型,无论哪个先构造都会移动值,而第二个参数将获得一个空壳。

于 2012-04-04T10:59:26.853 回答