右值引用 std::forward与完美转发

来源:
https://www.cnblogs.com/5iedu/p/7742589.html

  • 1. std::forward原型

template <typename T>
T&& forward(typename std::remove_reference<T>::type& param) //左值引用版本
{
    return static_cast<T&&>(param);
}

template <typename T>
T&& forward(typename std::remove_reference<T>::type&& param)  //右值引用版本
{
    //param被右值初始化时,T应为右值引用类型,如果T被绑定为左值引用则报错。
    static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"  
    " substituting _Tp is an lvalue reference type"); 
    
    return static_cast<T&&>(param);
}

//其中remove_reference的实现如下
//1. 特化版本(一般的类)
template <typename T>
struct remove_reference 
{
    typedef T type;
};

//2. 左值引用版本
template <typename T>
struct remove_reference<T&>
{
    typedef T type;
};

//3. 右值引用版本
template <typename T>
struct remove_reference<T&&>
{
    typedef T type;
};
  • 2. 完美转发(Perfect Forwarding)

(1)完美转发:是指在函数模板中,完全依照模板的参数类型(即保持实参的左值、右值特性),将实参传递给函数模板中调用的另外一个函数。

(2)原理分析

class Widget{};

//完美转发
template<typename T>
void func(T&& fparam) //fparam是个Universal引用
{
    doSomething(std::forward<T>(fparam));
}

//1. 假设传入func是一个左值的Widget对象, T被推导为Widget&,则forward如下:
Widget& && forward(typename std::remove_reference<Widget&>::type& param)
{
    return static_cast<Widget& &&>(param);
}
//==>引用折叠折后
Widget& forward(Widget& param)
{
    return static_cast<Widget&>(param);
}

//2. 假设传入func是一个右值的Widget对象, T被推导为Wiget,则forward如下:
Widget&& forward(typename std::remove_reference<Widget>::type& param)
{
    return static_cast<Widget&&>(param);
}

(3)std::forward和std::move的联系和区别

①std::move是无条件转换,不管它的参数是左值还是右值,都会被强制转换成右值。就其本身而言,它没有move任何东西。

②std::forward是有条件转换。只有在它的参数绑定到一个右值时,它才转换它的参数到一个右值。当参数绑定到左值时,转换后仍为左值。

③对右值引用使用std::move,对universal引用则使用std::forward

④如果局部变量有资格进行RVO优化,不要把std::move或std::forward用在这些局部变量中

⑤std::move和std::forward在运行期都没有做任何事情。

【编程实验】不完美转发和完美转发

#include <iostream>
//#include <utility> //for std::forward
using namespace std;

void print(const int& t)
{
    cout <<"lvalue" << endl;
}

void print(int&& t)
{
    cout <<"rvalue" << endl;
}

template<typename T>
void Test(T&& v) //v是Universal引用
{
    //不完美转发
    print(v);  //v具有变量,本身是左值,调用print(int& t)
    
    //完美转发
    print(std::forward<T>(v)); //按v被初始化时的类型转发(左值或右值)
    
    //强制将v转为右值
    print(std::move(v)); //将v强制转为右值,调用print(int&& t)
}

int main()
{
    cout <<"========Test(1)========" << endl; 
    Test(1); //传入右值
    
    int x = 1;
    cout <<"========Test(x)========" << endl;
    Test(x); //传入左值
    
    cout <<"=====Test(std::forward<int>(1)===" << endl;
    Test(std::forward<int>(1)); //T为int,以右值方式转发1
    //Test(std::forward<int&>(1)); //T为int&,需转入左值
    
    cout <<"=====Test(std::forward<int>(x))===" << endl;
    Test(std::forward<int>(x)); //T为int,以右值方式转发x
    cout <<"=====Test(std::forward<int&>(x))===" << endl;
    Test(std::forward<int&>(x)); //T为int,以左值方式转发x
    
    return 0;
}

输出结果

e:\Study\C++11\16>g++ -std=c++11 test2.cpp
e:\Study\C++11\16>a.exe
========Test(1)========
lvalue
rvalue
rvalue
========Test(x)========
lvalue
lvalue
rvalue
=====Test(std::forward<int>(1)===
lvalue
rvalue
rvalue
=====Test(std::forward<int>(x))===
lvalue
rvalue
rvalue
=====Test(std::forward<int&>(x))===
lvalue
lvalue
rvalue

  • 3.万能的函数包装器

(1)利用std::forward和可变参数模板实现

①可将带返回值、不带返回值、带参和不带参的函数委托万能的函数包装器执行。

②Args&&为Universal引用,因为这里的参数可能被左值或右值初始化。Funciont&&也为Universal引用,如被lambda表达式初始化。

③利用std::forward将参数正确地(保持参数的左、右值属性)转发给原函数

【编程实验】万能的函数包装器

#include <iostream>
using namespace std;

//万能的函数包装器
//可将带返回值、不带返回值、带参和不带参的函数委托万能的函数包装器执行

//注意:Args&&表示Universal引用,因为这里的参数可能被左值或右值初始化
//      Funciont&&也为Universal引用,如被lambda表达式初始化
template<typename Function, class...Args>
auto FuncWrapper(Function&& func, Args&& ...args)->decltype(func(std::forward<Args>(args)...))
{
    return func(std::forward<Args>(args)...);
}

void test0()
{
    cout << "void test0()" << endl;
}

int test1()
{
    return 1;
}

int test2(int x)
{
    return x;
}

string test3(string s1, string s2)
{
    return s1 + s2;
}

int main()
{
    
    FuncWrapper(test0);
    
    cout << "int test1(): "; 
    cout << FuncWrapper(test1) << endl;
    
    cout << "int test2(int x): " ;
    cout << FuncWrapper(test2, 1) << endl;
    
    cout << "string test3(string s1, string s2): ";
    cout << FuncWrapper(test3, "aa", "bb") << endl;
    
    cout << "[](int x, int y){return x + y;}: ";
    cout << FuncWrapper([](int x, int y){return x + y;}, 1,  2) << endl;
    
    return 0;
}

输出结果:

e:\Study\C++11\16>g++ -std=c++11 test3.cpp
e:\Study\C++11\16>a.exe
void test0()
int test1(): 1
int test2(int x): 1
string test3(string s1, string s2): aabb
[](int x, int y){return x + y}: 3

(2)emplace_back减少内存拷贝和移动

①emplace_back的实现原理类似于“万能函数包装器”,将参数std::forward转发给元素类的构造函数。实现上,首先为该元素开辟内存空间,然后在这片空间中调用placement new进行初始化,这相当于“就地”(在元素所在内存空间)调用元素对象的构造函数。

②而push_back会先将参数转为相应的元素类型,这需要调用一次构造函数,再将这个临时对象拷贝构造给容器内的元素对象,所以共需要一次构造和一次拷贝构造。从效率上看不如emplace_back,因为后者只需要一次调用一次构造即可。

③一般传入emplace_back的是构造函数所对应的参数(也只有这样传参才能节省一次拷贝构造),所以要求对象有相应的构造函数,如果没有对应的构造函数,则只能用push_back,否则编译会报错。如emplace_back(int, int),则要求元素对象需要有带两个int型的构造函数。

【编程实验】emplace_back减少内存拷贝和移动

#include <iostream>
#include <vector>

using namespace std;

class Test
{
    int m_a;
public:
    static int m_count;
    
    Test(int a) : m_a(a)
    {
        cout <<"Test(int a)" << endl;
    }
    
    Test(const Test& t) : m_a(t.m_a)
    {
        ++m_count;
        cout << "Test(const Test& t)" << endl;
    }
    
    Test& operator=(const Test& t)
    {
        this->m_a = t.m_a;
        return *this;
    }
};

int Test::m_count = 0;

int main()
{
    //创建10个值为1的元素
    Test::m_count = 0;
    vector<Test> vec(10, 1); //首先将1转为Test(1),会调用1次Test(int a)。然后,利用Test(1)去拷贝构造10个元素,所以
                             //调用10次拷贝构造。
    cout << "vec.capacity():" << vec.capacity() << ", "; //10
    cout << "vec.size():" << vec.size() <<  endl;        //10,空间己满
    
    Test::m_count = 0;
    vec.push_back(Test(1)); //由于capacity空间己满。首先调用Test(1),然后再push_back中再拷贝
                            //构造10个元素(而不是1个,为了效率),所以调用10次拷贝构造
    cout << "vec.capacity():" << vec.capacity() << ", ";  //20
    cout << "vec.size():" << vec.size() <<  endl;         //11,空间未满
    
    Test::m_count = 0;
    vec.push_back(1);  //先调用Test(1),然后调用1次拷贝构造
    cout << "vec.capacity():" << vec.capacity() << ", "; //20
    cout << "vec.size():" << vec.size() <<  endl;         //12,空间未满
    
    Test::m_count = 0;
    vec.emplace_back(1); //由于空间未满,直接在第12个元素位置调用placement new初始化那段空间
                         //所以就会调用构造函数,节省了调用拷贝构造的开销
    cout << "vec.capacity():" << vec.capacity() << ", "; //20
    cout << "vec.size():" << vec.size() <<  endl;        //13,空间未满
    
    Test::m_count = 0;
    vec.emplace_back(Test(1)); //先调用Test(1),再调用拷贝构造(注意与vec.emplace_back(1)之间差异)
    cout << "vec.capacity():" << vec.capacity() << ", "; //20
    cout << "vec.size():" << vec.size() <<  endl;        //14,空间未满
    
    return 0;
}

输出结果

e:\Study\C++11\16>g++ -std=c++11 test4.cpp
e:\Study\C++11\16>a.exe
Test(int a)
...  //中间省略了调用10次Test(const Test& t)
vec.capacity():10, vec.size():10
Test(int a)
...  //中间省略了调用10次Test(const Test& t)
vec.capacity():20, vec.size():11
Test(int a)
Test(const Test& t)
vec.capacity():20, vec.size():12
Test(int a)
vec.capacity():20, vec.size():13
Test(int a)
Test(const Test& t)
vec.capacity():20, vec.size():14

在这里插入图片描述

### 回答1: c++中的std::move和std::forward都是用于实现完美转发的工具。 std::move是将一个左值强制转换为右值引用,从而实现将资源所有权从一个对象转移到另一个对象的目的。使用std::move可以避免不必要的复制和赋值操作,提高程序的效率。 std::forward则是用于在函数模板中实现完美转发,将参数按照原来的类型转发给下一个函数。它可以保证参数的类型和值被完美转发,避免了不必要的拷贝和移动操作,提高了程序的效率。 总的来说,std::move和std::forward都是用于提高程序效率和避免不必要的拷贝和移动操作的工具。 ### 回答2: C++标准库中提供了两个模板函数std::move和std::forward,它们在C++11中引入,用于实现移动语义和完美转发std::move的作用是将一个左值强制转换为右值引用,使得该对象的所有权能够被转移,而不是进行复制或者赋值。通过调用移动构造函数或者移动赋值运算符来减少开销。移动语义是C++11中的一个重要特性,它可以提高程序的效率并且使得程序更加高效。 std::forward的作用是实现完美转发,将函数参数原封不动地转发到另一个函数中,使得函数模板可以保持参数类型和实参类型一致。std::forward用于实现通用类型的泛型编程,解决了模板函数中参数类型无法确定的问题。 实际上,std::move和std::forward的实现方式都非常简单,都是使用了static_cast进行类型转换。但是它们在C++11中的引入,以及其实现的本质却给C++程序的效率提高和泛型编程提供了重要的支持。 总之,std::move和std::forward是C++11中非常重要的语言特性,它们可以帮助程序员实现移动语义和完美转发,提高程序的性能和可读性。要注意正确使用它们,以避免出现不必要的开销和错误。 ### 回答3: C++ 11中引入了两个新的特殊函数模板std::move()和std::forward(),用来实现完美转发和移动语义,提高了代码的效率和简洁性。 std::move的作用就是将一个左值转换成右值引用,将左值的所有权抢过来,但不进行任何内存拷贝。通常用于移动语义,可以提高程序的效率。用法很简单,就是std::move(左值变量)。比如,若有个vector<int> a和一个vector<int> b,我想把b中的元素全部移动到a中,可以这样写:a.insert(a.end(), std::make_move_iterator(b.begin()), std::make_move_iterator(b.end()));这里,std::make_move_iterator()是一个语法糖,将它们的元素包装成可以引用的右值。 std::forward的作用是保持参数本来的类型(左值或右值),既可以接收左值也可以接收右值,并将参数传递给其他函数,这就是所谓的完美转发完美转发可以达到只有一个函数就可以处理所有情况的目的。用法就是std::forward<参数类型>(参数变量)。比如,若有个函数template<class T> void f(T&& t),其中参数t是万能引用,需要把t传递给其他函数g(),我们可以这样写:g(std::forward<T>(t));这样就可以达到完美转发的目的。 需要注意的是,std::move和std::forward虽然看起来相似,但作用是不同的,std::move是将左值转换成右值引用,而std::forward是维持参数的原类型,用于完美转发。同时,它们都需要加上相应的模板类型,以便让编译器进行类型推导。在使用时,需要根据情况选择合适的函数,以达到更好的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值