Loading... > 判断使用了&& 之后到底是万能引用类型还是右值类型,就直接传左值和右值进去,查看是否有编译报错 # 一、类型区别基本含义 ```cpp void func1(const int& abc) {} // abc的类型是const int& template <typename T> void func2(const T& abc) {} //T = int ,abc = const int& int main() { func1(10); func2(10);//编译器会推断出 //T = int ,abc = const int& T其实不仅仅取决于10,还取决于abc的类型 return 0; } const&可以接左值也可以接右值 ``` # 二、universal reference / 万能引用 / 未定义引用基本认识 > 结论:万能引用是一种类型。就跟int是一种类型是一个道理。再次强调,万能引用是一种类型。 ## 1、右值引用(全称:右值引用类型)是用&&符号表示 ```cpp //myfunc3(i); 传入左值的话就会报错 void myfunc3(int&& tmprv) //参数tmprv是个右值引用类型 { cout << "myfunc3 : "<< tmprv << endl; return; } int main() { myfunc3(10); //正确,右值做实参。 int i = 100; //i左值 myfunc3(i); //错,右值 引用不能接(绑)左值。 return 0; } ``` 只能传右值,无法传左值。 解决办法:使用万能引用 ## 2、万能引用 使用万能引用的话,就能传右值,也能传左值,这也是检查是否是万能引用的方法,如果某种情况编译不过,就说明不是万能引用 ```cpp //无论传入右值还是左值,都不会报错 从编译器没有报错可以看出应该T推断的不是int类型 template <typename T> void myfunc4(T&& tmprv) //注意,&&是属于tmprv类型的一部分,不是T类型的一部分(&&和T类型没有关系) { tmprv = 12; //不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值。因为tmprv本身是个左值。 cout << tmprv << endl; return; } int main() { myfunc4(10); //正确,右值做实参。 int i = 100; //i左值 myfunc4(i); // **左值被传递,因此tmprv是个左值引用,也就是int& 最终i 为12** ii = 200; myfunc4(std::move(ii));//**右值被传递,因此tmprv是个右值引用,也就是int&&,最终i值变成12**; printf("ii = %d\\n", ii);// i的值变为12 return 0; } ``` tmprv = 12; //不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值。因为tmprv本身是个左值。 其中i 和 ii都能被改变值 当myfunc4调用时实参为类型T的左值,那么模板T会被展开为T& ```cpp void myfunc4(T&& tmprv) { tmprv = 12; //不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值。因为tmprv本身是个左值。 cout << tmprv << endl; return; } 当使用myfunc4(i);调用时,会被推断为 void myfunc4(int& && tmprv) { tmprv = 12; //不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值。因为tmprv本身是个左值。 cout << tmprv << endl; return; } 引用折叠后为: void myfunc4(int& tmprv) 它就可以匹配左值了 { tmprv = 12; //不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值。因为tmprv本身是个左值。 cout << tmprv << endl; return; } ``` 当myfunc4调用时实参为类型T的右值,那么模板T会被展开为T ```cpp void myfunc4(T&& tmprv) { tmprv = 12; //不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值。因为tmprv本身是个左值。 cout << tmprv << endl; return; } 最终匹配为右值: void myfunc4(int&& tmprv) { tmprv = 12; //不管tmprv的类型是左值引用还是右值引用,都可以给tmprv赋值。因为tmprv本身是个左值。 cout << tmprv << endl; return; } ``` **万能引用离不开两种语境**: `必须是函数模板` `必须是发生了模板类型推断并且函数模板形参长这样:T&&` 如果实参传递了一个整型左值给形参,tmprv的类型最终会被推断为int &类型。 如果果实参传递了一个整型右值给形参,tmprv的类型最终会被推断为int &&类型。 结论:`T&&是一个万能 引用类型`。 ## 3、什么情况下才是万能引用 只有下面这两种情况才能是万能引用 * 一个是函数模板中用作函数参数的类型推断(参数中要涉及到类型推断),T&& * **auto &&tmpvalue = ..... 也是万能引用,这个后续再谈**。 * 其他情况的&&,都是右值引用。 ```cpp template <typename T> void myfunc5(std::vector<T>&& param) { cout << "myfunc5函数模板" << endl; } int main() { std::vector<int> aa = { 1 }; myfunc5(aa);//这样就会编译报错 因为void func(std::vector<T>&& param)不是万能引用 myfunc5(std::move(aa)); } ``` myfunc5可以传入左值,但不能传入右值,所以void myfunc5(std::vector<T>&& param) **不是万能引用**。 ## 4、万能引用资格的剥夺 > 下面例子中的两种情况会导致万能引用失效: ```cpp template <typename T> void myfunc6(const T&& tmprv) //有const修饰,万能引用资格被剥夺,因为&&,所以只能是个右值引用 { cout << tmprv << endl; return; } template <typename T> class mytestc { public: void testfunc(T&& x) {}; //这个不是万能引用,而是右值引用,因为 testfunc成员函数本身没有涉及到类型推断。 }; int main() { //万能引用已失效 //case1: 剥夺:const会剥夺 一个引用成为万能引用的资格,被打回原型成右值引用 int i = 100; //_nmsp1::myfunc6(i); //不可以,只能传递右值进去 编译报错提示无法将int转化为 const T&&类型 myfunc6(std::move(i)); //不可以,只能传递右值进去,必须是std::move(i); //case2: 类模板的成员函数会导致万能引用失效 因为成员函数本身并没有涉及到类型推断 mytestc<int> mc; int ii = 100; mc.testfunc(std::move(ii)); //错,左值不能绑定到右值引用上,必须修改为std::move(i); return 0; } ``` 解决上面的case2中的问题: ```cpp template <typename T> class mytestc { public: void testfunc(T&& x) {}; public: template <typename T2> //T2是独立的和 T没有关系 void testfunc2(T2&& x) {}; }; int main() { mytestc<int> mc; int ii = 100; mc.testfunc2(100); // T2被推导为 int&& mc.testfunc2(ii); // T2被推导为 int& return 0; } ``` # 三、完美转发在其他场景的应用 ## 1. 在构造函数模板中使用完美转发范例 ```cpp class Human1 { public: ////构造函数 Human1(const string& tmpname) :m_sname(tmpname) { cout << "Human1(const string &tmpname)执行" << endl; } //Human(string&& tmpname) :m_sname(tmpname) Human1(string&& tmpname) :m_sname(std::move(tmpname)) //move并不具备移动能力,把一个左值转换成一个右值。 { cout << "Human1(string&& tmpname)执行" << endl; } string m_sname; }; int main() { //case1:测试构造函数的完美转发 string sname1 = "ZhangSan"; Human1 myhuman1(sname1);//调用构造函数 Human1 myhuman2(string("LiSi")); //调用移动构造函数 "LiSi"是const char[5]类型,而string("LiSi")是string类型。 string sname11 = "ZhangSan"; Human11 myhuman11(sname11);//调用构造函数 Human11 myhuman22(string("LiSi")); //调用移动构造函数 "LiSi"是const char[5]类型,而string("LiSi")是string类型。 return 0; } ``` 使用完美转发,就能替换构造函数 万能引用和完美转发之间的关系? ## 2. 在拷贝构造函数模板中使用完美转发范例的坑 ```cpp class Human2 { public: //构造函数模板 template<typename T> Human2(T&& tmpname) : m_sname(std::forward<T>(tmpname)) { cout << "Human2(T&& tmpname)执行" << endl; } //拷贝构造函数 Human2(const Human2& th) : m_sname(th.m_sname) { cout << "Human2(const Human2& th)拷贝构造函数执行" << endl; } //移动构造函数 Human2(Human2&& th) : m_sname(std::move(th.m_sname)) { cout << "Human2(Human2&& th)移动构造函数执行" << endl; } //private: string m_sname; }; int main() { //case2:测试当存在拷贝构造和移动构造的时候 string sname1 = "ZhangSan"; Human2 myhuman1(sname1);//调用构造函数 /* 3_3_3.cpp:94:57: error: no matching function for call to ‘std::__cxx11::basic_string<char>::basic_string(_nmsp1::Human2&)’ 94 | Human2(T&& tmpname) : m_sname(std::forward<T>(tmpname)) | ^ */ //单独写的拷贝构造函数 还是会去调用template 导致编译报错 //Human2 myhuman3(myhuman1); //实际编译器去调用了构造函数模板,而不是调用了拷贝构造函数。 std::enable_if //单独写的移动构造函数 就会调用移动构造函数 Human2 myhuman4(std::move(myhuman1));//会调用单独的移动构造函数 } ``` 最后修改:2025 年 07 月 01 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏