根据我个人的理解,引用应当是是C++为了实现与C中的指针相同功能的一种封装。右值引用,就是为了完整实现原有C中的“指针功能”。
传统的引用往往就是左值引用,比如:
int gen = 1; int& ref_gen = gen; const int& conref_gen = gen;
左值引用(int& ),一般用于需要在函数内成员对象,比如std::swap。
常量左值引用(const int &),一般用于传入一个较大的数据的时候,比如一个巨大的结构体,比如在传入的时候的拷贝工作。
但是,右值引用解决的一种具体的情形是这样的,如果一个临时的变量(传完即销毁)传入其中,那么就不需要再拷贝一次内存进行赋值,只需要把指针指向临时变量的地址即可。这种,用指针非常容易实现,但是如果使用引用的方式就非常难,因此右值引用就可以避免一次拷贝的过程,最直接的影响就是可以加快程序的运行速度。
class Info { private: char* _data; public: Info(const char* str = 0) { _data = new char[strlen(str) + 1]; strcpy(_data, str); } Info(const Info& cur) { _data = new char[strlen(cur._data) + 1]; strcpy(_data, cur._data); } Info(Info&& cur) noexcept { _data = cur._data; cur._data = nullptr; } }; int main() { Timer CurTimer; CurTimer.StartTimer(); for (int i=0;i<100000;i++) { Info cur("10000000086"); Info l(cur); } CurTimer.StopTimer(); std::cout << "left: " << CurTimer.GetTimerMilliSec() << std::endl; CurTimer.StartTimer(); for (int i = 0; i < 100000; i++) { Info cur("10000000086"); Info r(std::move(cur)); } CurTimer.StopTimer(); std::cout << "right: " << CurTimer.GetTimerMilliSec() << std::endl; return 0; }
通用引用
当右值引用和模板类型和auto搭配的时候,T&&不一定代表右值引用,也可能是一个左值引用。具体的类型跟初始化的内容有关,初始化是左值引用,那么之后的类型就是左值引用。(以下的程序说明,左值引用和原值的地址,而右值引用的内容是暂时存储到了一个新的地址里面。
template<typename T> void func(T&& foo) { std::cout << &foo << std::endl; } int main() { int value = 10; int& ref = value; int&& lef = value * 1; func(ref); func(lef); std::cout << &value << std::endl; } /* output: 000000E3425EF664 000000E3425EF6C4 000000E3425EF664 */
完美转发
如果将一个参数交给另外一个函数处理,那么那么另一个函数会不知道这个是左值还是右值。int && a并不是一个右值,而是一个绑定了右值对象的左值。因此,需要有一个函数使得传递的函数常数的类型。(以下的程序可以很好的说明参数在传递过程的类型变化。
#include <iostream> template<typename T> void typeprint(T& _) { std::cout << "left value" << std::endl; } template<typename T> void typeprint(T&& _) { std::cout << "right value" << std::endl; } template<typename T> void testForward(T&& v) { typeprint(v); typeprint(std::forward<T>(v)); typeprint(std::move(v)); } int main() { testForward(1); std::cout << std::endl; int x = 1; testForward(x); } /* output: left value right value right value left value left value right value */
STL中也使用右值引用作为参数来加速,比如emplace_back,根据cppreference可以看到,当然构造函数也是需要经过处理的,就跟给出的例子一样。
std::move 是将原本的左值临时显式的变为右值,之后原本的左值应当应当被销毁。
std::forward 用于完美转发的函数。