繁体   English   中英

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

[英]Are there any use cases for std::forward with a prvalue?

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

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

这里param是一个lvaluestd::forward最终将它转换为rvalue或lvalue,具体取决于与它绑定的参数。

从cppreference.com看到std::forward定义我发现还有一个rvalue重载

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

任何人都可以给我任何理由为什么rvalue超载? 我看不到任何用例。 如果要将rvalue传递给函数,可以按原样传递它,不需要在其上应用std::forward

这与std::move不同,我在这里看到为什么还要一个rvalue重载:你可能会处理通用代码,在这些代码中你不知道你传递了什么,并且你想要无条件支持移动语义,参见eg 为什么std :: move采用通用引用?

编辑为了澄清这个问题,我问为什么从这里需要重载(2) ,以及它的用例。

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

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

我们有std::forward 2次重载

#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,没有它就会出现编译错误

请注意,如果我们不使用前进

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

你得到:

  1. 编译错误

如你所见,如果你只使用两个forward重载中的一个,你基本上导致不能编译4个案例中的2个,而如果你根本不使用forward ,你将只能编译4个案例中的3个。

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

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

简而言之:

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

#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

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

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

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

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

我之前盯着这个问题,读过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()的调用返回一个rvalue。 但是,假设代码是通用的:

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

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

该死的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM