是否允许`std :: function`移动它的参数? [英] Is `std::function` allowed to move its arguments?

查看:98
本文介绍了是否允许`std :: function`移动它的参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在研究这个问题时,我注意到GCC(v4.7)的实现 std :: function 在按值创建时移动它的参数。以下代码显示了此行为:

  #include< functional> 
#include< iostream>

struct CopyableMovable
{
CopyableMovable(){std :: cout<< 默认<< \\\
; }
CopyableMovable(CopyableMovable const&){std :: cout<< 复制<< \\\
; }
CopyableMovable(CopyableMovable&& amp;){std :: cout<< 移动<< \\\
; }
};

void foo(CopyableMovable cm)
{}

int main()
{
typedef std :: function< void(CopyableMovable )>根据值;

byValue fooByValue = foo;

CopyableMovable cm;
fooByValue(cm);
}
//输出:默认复制移动移动

我们在这里看到执行 cm 的副本(这似乎是合理的,因为 byValue 的参数是按值取值的),但随后有两个动作。由于函数 cm 的副本上运行,因此它移动其参数的事实可被视为不重要的实现详情。但是,当将函数) > bind :

  #include< functional> 
#include< iostream>

struct MoveTracker
{
bool hasBeenMovedFrom;

MoveTracker()
:hasBeenMovedFrom(false)
{}
MoveTracker(MoveTracker常量&)
:hasBeenMovedFrom(false)
{}
MoveTracker(MoveTracker&& other)
:hasBeenMovedFrom(false)
{
if(other.hasBeenMovedFrom)
{
std: :cout<< 已经移动! << \\\
;
}
else
{
other.hasBeenMovedFrom = true;
}
}
};

foo(MoveTracker,MoveTracker){}
$ b $ int main()
{
using namespace std :: placeholders;
std :: function< void(MoveTracker)> func = std :: bind(foo,_1,_1);
MoveTracker obj;
func(obj); //打印已经移动!
}

标准允许这种行为吗?是否允许 std :: function 移动其参数?如果是这样,我们可以通过将 bind 返回的包装转换为 std :: function -value参数,尽管这会在处理多次出现的占位符时触发意外的行为?

解决方案

std :: function 被指定用 std :: forward 传递提供的参数给包装函数。例如对于 std :: function< void(MoveTracker)> ,函数调用操作符相当于

  void operator(CopyableMovable a)
{
f(std :: forward< CopyableMovable>(a));由于 std :: forward< T>





>当$ T 不是引用类型时,code>相当于 std :: move ,这占一个你的第一个例子中的举动。有可能第二个来自 std :: function 中的间接层。



然后也解释了使用 std :: bind 作为包装函数遇到的问题: std :: bind 指定转发它的参数,在这种情况下,它将在<$ c中调用由 std :: forward 调用产生的右值引用$ C>的std ::功能。因此,bind表达式的函数调用运算符会将右值引用转发给每个参数。不幸的是,既然你已经重用了占位符,那么在这两种情况下,它都是对同一对象的右值引用,所以对于可移动类型,首先构造的将移动该值,而第二个参数将获得空壳。 b $ b

While working on this question, I noticed that GCC (v4.7)'s implementation of std::function moves its arguments when they are taken by value. The following code shows this behavior:

#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

We see here that a copy of cm is performed (which seems reasonable since the byValue's parameter is taken by value), but then there are two moves. Since function is operating on a copy of cm, the fact that it moves its argument can be seen as an unimportant implementation detail. However, this behavior causes some trouble 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!"
}

Is this behavior allowed by the standard? Is std::function allowed to move its arguments? And if so, is it normal that we can convert the wrapper returned by bind into a std::function with by-value parameters, even though this triggers unexpected behavior when dealing with multiple occurrences of placeholders?

解决方案

std::function is specified to pass the supplied arguments to the wrapped function with std::forward. e.g. for std::function<void(MoveTracker)>, the function call operator is equivalent to

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

Since std::forward<T> is equivalent to std::move when T is not a reference type, this accounts for one of the moves in your first example. It's possible that the second comes from having to go through the indirection layers inside std::function.

This then also accounts for the problem you are encountering with using std::bind as the wrapped function: std::bind is also specified to forward its parameters, and in this case it is being passed an rvalue reference resulting from the std::forward call inside std::function. The function call operator of your bind expression is thus forwarding an rvalue reference to each of the arguments. Unfortunately, since you've reused the placeholder, it's an rvalue reference to the same object in both cases, so for movable types whichever is constructed first will move the value, and the second parameter will get an empty shell.

这篇关于是否允许`std :: function`移动它的参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆