【问题标题】:Is `std::function` allowed to move its arguments?`std::function` 是否允许移动其参数?
【发布时间】: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&amp; 类型的左值引用,并将作为对foo 的左值引用转发两次,结果是在这些左值引用的两个副本构造中。

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


【解决方案1】:

std::function 指定将提供的参数传递给带有std::forward 的包装函数。例如对于std::function&lt;void(MoveTracker)&gt;,函数调用运算符等价于

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

由于std::forward&lt;T&gt;T 不是引用类型时等价于std::move,因此这说明了第一个示例中的移动之一。第二个可能来自必须通过std::function 内部的间接层。

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

【讨论】:

  • 哦,我从来没有意识到std::forward&lt;NotRef&gt; 等同于std::move&lt;NotRef&gt;!这解释了很多事情。
猜你喜欢
  • 1970-01-01
  • 2017-12-22
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
  • 2018-06-01
  • 2017-03-08
相关资源
最近更新 更多