是否有使用prvalue的std :: forward的用例?

时间:2015-04-25 00:40:29

标签: c++ c++11 move-semantics rvalue-reference

std::forward最常见的用法是完美转发转发(通用)引用,例如

template<typename T>
void f(T&& param)
{
    g(std::forward<T>(param)); // perfect forward to g
}

此处paramlvaluestd::forward最终会将其转换为右值或左值,具体取决于与其绑定的参数。

查看definition of std::forward from cppreference.com我发现还有rvalue重载

template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );

任何人都可以告诉我rvalue超载的原因吗?我看不到任何用例。如果你想将一个右值传递给一个函数,你可以按原样传递它,不需要在它上面应用std::forward

这与std::move不同,在那里我看到为什么人们也想要rvalue超载:您可以处理通用代码,在这些代码中您不知道自己是什么传递,你想要无条件支持移动语义,参见例如Why does std::move take a universal reference?

编辑为了澄清这个问题,我问为什么overload (2) from here是必要的,以及一个用例。

3 个答案:

答案 0 :(得分:1)

好的,因为@vsoftco要求提供简洁的用例,这里是一个精致的版本(使用他的想法让“my_forward”真正看到超载被调用)。

我通过提供一个代码示例来解释“用例”,该代码示例没有prvalue无法编译或行为不同(无论这样做是否真的有用)。

我们有2 overloads for std::forward

#include <iostream>

template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type& t) noexcept
{
    std::cout<<"overload 1"<<std::endl;
    return static_cast<T&&>(t);
}

template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type&& t) noexcept
{
    std::cout<<"overload 2"<<std::endl;
    static_assert(!std::is_lvalue_reference<T>::value,
              "Can not forward an rvalue as an lvalue.");
    return static_cast<T&&>(t);
}

我们有4个可能的用例

用例1

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &&
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(v)); // &
    return 0;
}

用例2

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &&
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(std::move(v))); //&&
    return 0;
}

用例3

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &
    Library( vector<int> a):b(a){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(v)); // &
    return 0;
}

用例4

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &
    Library( vector<int> a):b(a){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(std::move(v))); //&&
    return 0;
}

这是简历

  1. 使用了重载1,没有它就会出现编译错误
  2. 使用了重载2,没有它就会出现编译错误
  3. 使用重载1,但没有编译错误
  4. 使用了重载2,没有它就会出现编译错误
  5. 请注意,如果我们不使用前进

    Library a( std::move(v));
    //and
    Library a( v);
    

    你得到:

    1. 编译错误
    2. 编译
    3. 编译
    4. 编译
    5. 如您所见,如果您只使用两个forward重载中的一个,则基本上导致无法编译4个案例中的2个,而如果您根本不使用forward,则会得到仅编译4个案例中的3个。

答案 1 :(得分:0)

这个答案是为了回答@vsoftco的评论

  

@DarioOO感谢您的链接。你能写一个简洁的答案吗?从你的例子来看,我还不清楚为什么还需要为rvalues定义std :: forward

简而言之:

因为没有右值专门化,以下代码将无法编译

#include <utility>
#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // hi! only rvalue here :)
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    A a( forward<vector<int>>(v));
    return 0;
}

然而,我无法拒绝输入更多内容,所以这里也不是答案的简单版本。

长版:

您需要移动v,因为类Library没有构造函数接受左值,但只有rvalue引用。 如果没有完美的转发,我们最终会出现不良行为:

  

包装函数在传递重物时会产生高性能的阴谋。

使用移动语义,我们确保使用移动构造函数,如果可能的话。 在上面的示例中,如果我们删除std::forward代码将无法编译。

那么实际在做什么forward?没有我们的共识,移动元素?都能跟得上!

它只是创建矢量的副本并移动它。我们怎么能确定呢?只需尝试访问该元素。

vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v)); //what happens here? make a copy and move
std::cout<<v[0];     // OK! std::forward just "adapted" our vector

如果您改为移动该元素

vector<int> v;
v.push_back(1);
A a( std::move(v)); //what happens here? just moved
std::cout<<v[0];  // OUCH! out of bounds exception

因此需要重载才能实现仍然安全的隐式转换,但如果没有过载则无法实现。

事实上,以下代码将无法编译:

vector<int> v;
v.push_back(1);
A a( v); //try to copy, but not find a lvalue constructor

真实用例:

您可能会争辩说,转发参数可能会创建无用的副本,从而隐藏可能的性能损失,是的,这实际上是真的,但考虑实际用例:

template< typename Impl, typename... SmartPointers>
static std::shared_ptr<void> 
    instancesFactoryFunction( priv::Context * ctx){
        return std::static_pointer_cast<void>( std::make_shared<Impl>(

                std::forward< typename SmartPointers::pointerType>( 
            SmartPointers::resolve(ctx))... 
            )           );
}

代码取自我的框架(第80行):Infectorpp 2

在这种情况下,参数从函数调用转发。 SmartPointers::resolve的返回值无论Impl的构造函数接受rvalue还是左值(因此没有编译错误,而且无论如何都被移动)都会被正确移动。

基本上你可以在任何情况下使用std::foward,你想让代码更简单,更易读,但你必须记住2点

  • 额外编译时间(实际上并非如此)
  • 可能会导致不需要的副本(当您没有明确地将某些内容移动到需要rvalue的内容时)

如果小心使用是一种强大的工具。

答案 2 :(得分:0)

之前我盯着这个问题,读过Howard Hinnant的链接,经过一个小时的思考后无法完全理解。现在我正在寻找并在五分钟内得到答案。 (编辑:得到答案太慷慨了,因为Hinnant的链接有答案。我的意思是我理解,并且能够以更简单的方式解释它,希望有人会发现有用的。)

基本上,这允许您在某些情况下是通用的,具体取决于传入的类型。请考虑以下代码:

#include <utility>
#include <vector>
#include <iostream>
using namespace std;

class GoodBye
{
  double b;
 public:
  GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
  GoodBye( const double& a):b(a){ std::cerr << "copy"; }
};

struct Hello {
  double m_x;

  double & get()  { return m_x; }
};

int main()
{
  Hello h;
  GoodBye a(std::forward<double>(std::move(h).get()));
  return 0;
}

此代码打印“移动”。有趣的是,如果删除std::forward,它会打印副本。对我来说,这很难让我全神贯注,但让我们接受并继续前进。 (编辑:我想这会发生,因为get会返回一个左值引用到rvalue。这样的实体衰变成一个左值,但是std :: forward会把它变成一个右值,就像常用的forward一样。仍觉得不直观虽然)。

现在,让我们想象另一个类:

struct Hello2 {
  double m_x;

  double & get() & { return m_x; }
  double && get() && { return std::move(m_x); }
};

假设在main的代码中,h是Hello2的一个实例。现在,我们不再需要std :: forward,因为对std::move(h).get()的调用会返回一个右值。但是,假设代码是通用的:

template <class T>
void func(T && h) {
  GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}

现在,当我们致电func时,我们希望它与HelloHello2一起正常运行,即我们想要触发一次行动。如果我们包含外部Hello,则仅在std::forward的右值发生这种情况,因此我们需要它。但是......我们得到了一个妙语。当我们将Hello2的rvalue传递给此函数时,get()的rvalue重载将返回rvalue double,因此std::forward实际上正在接受rvalue。因此,如果没有,您将无法像上面那样编写完全通用的代码。

该死。