自学内容网 自学内容网

C++11--右值引用

1.引用

右值引用是在C++11中所引进的,在前面,我们已经了解并使用过了引用,引用的作用就是给某一个变量取别名,但实际上,我们使用的引用是左值引用,而我们现在要了解的是与左值引用相对应的右值引用。

无论左值引用还是右值引用,都是给对象取别名。为了更好的区别和了解左值引用和右值引用,我们需要理解两种引用的使用与区别。

2.左值引用和右值引用

2.1左值引用

左值是一个表示数据的表达式(如变量名或解引用的指针),通常可以修改的变量我们都称之为左值,我们可以获取它的地址+可以对它赋值

左值引用通常是给左值的引用,给左值取别名。

正如赋值符号 = 的左边是被赋值改变的变量,而左值可以出现赋值符号的左边,但右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

 2.2右值引用

右值也是一个表示数据的表达式,但与左值相对,右值一般是不可修改的常量或者不等式、函数返回值等临时对象。如:基本类型的常量、表达式返回值,函数返回值等等。

右值引用通常是对右值的引用,给右值取别名。

相比于左值,右值可以出现在赋值符号 = 的右边,但是不能出现出现在赋值符号的左边,右值不能取地址

2.3左值引用和右值引用的使用

2.31左值引用的使用

左值引用的使用格式是给对应左值取别名前加上一个&符号,前面我们也已经学过。

格式:左值变量类型 & 别名名称  =  左值

需要注意的是,左值引用一般情况下大多数都是给左值取别名,但是也可以给右值取别名。例如使用const修饰别名类型,使得其具有常性,不可被修改,达到给右值取别名的效果。

2.32右值引用的使用 

相比于左值引用的使用,右值引用的使用是在别名前加两个 & 符号,也就是 && ,再给对应的右值取别名。

格式:右值变量类型 && 别名名称  =  右值

同样的,右值引用正常情况大对数都是用于对右值取别名,但通过特殊手段,使用 move()也是可以给左值取别名的。

2.4左值引用与右值引用的比较

左值引用:

  • 一般只能引用左值,不能直接引用右值
  • const 左值引用可以将别名变得具有常性,使得const左值引用可以给右值取别名。
  • 左值:通常可以修改的变量我们都称之为左值。

右值引用:

  • 一般只能引用右值,不能直接引用左值
  • 通过move左值改变值的属性,右值引用可以给左值取别名。
  • 右值:一般是不可修改的常量或者不等式、函数返回值等临时对象。

3.引用的使用场景

了解了左右值引用如何使用后,我们需要知道左右值两种引用具体是使用在哪些地方,有各自具有怎样的优势。

3.1左值引用

左值引用的主要作用实则是比较好知道的。

对于左值引用的参数与函数返回值,其传递的效率都是十分高的,这是左值引用使用的场景。这点可以与指针想比较,两者是相似的。

左值引用的取的别名就相当于变量本身,可以直接通过对别名的修改直接影响引用左值本身,尤其是其作为函数返回值与参数,不需要拷贝临时对象,这是十分高效的。而左值引用作在类中的成员函数作为函数参数与返回值是十分常见的。

不足之处:

对于函数体中产生的临时对象,其不能直接使用左值引用去作为返回值。这是因为函数体内产生的临时对象出了函数本身作用域将会被销毁,但是如果其作为左值引用为返回值,其得到的返回值的空间将是已经被销毁的空间,将会产生难以预料的错误。

3.2右值引用

对于左值引用的不足之处,右值引用的出现很好的解决了其缺点。

在类的中,有资源开销的对象,其拷贝构造与赋值重载的使用通常是需要进行深拷贝的,而对于临时变量而言,对其使用深度拷贝实际上是十分浪费效率的,临时变量中存储的内存资源是使用完后即将要销毁的,这并不能很好的利用临时对象的资源。而右值引用的出现解决则很好的解决了临时对象资源利用的问题。

3.21移动拷贝和移动赋值

移动拷贝和移动赋值是右值引用出现的主要原因,他解决了类中对临时对象空间资源的利用。

右值实际上分成纯右值和将亡值。纯右值是基本类型的常量或临时对象,而将亡值是自定义类型的临时对象,使用过后将会自动被析构销毁掉。

class String
{
private:
char* _str;
public:
String(const char* str = "")
{
_str = new char[strlen(str) + 1];//多开辟一个字节空间存放'\0'
//strcpy(_str, str);
for (int i = 0; i <= strlen(str); ++i)
{
_str[i] = str[i];
}
}
void swap(String& s)
{
::swap(_str, s._str);
}
// 拷贝构造
String(const String& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 深拷贝" << endl; \

_str = new char[strlen(s._str) + 1];
for (int i = 0; i <= strlen(s._str); ++i)
{
_str[i] = s._str[i];
}
}
// 赋值重载
String& operator=(const String& s)
{
cout << "string& operator=(string s) -- 深拷贝赋值" << endl;

_str = new char[strlen(s._str) + 1];
for (int i = 0; i <= strlen(s._str); ++i)
{
_str[i] = s._str[i];
}
return *this;
}
// 移动构造
String(String&& s)
:_str(nullptr)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);//交换临时的将亡值对象的资源
}
// 移动赋值
String& operator=(String&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);//交换临时的将亡值对象的资源
return *this;
}
~String()
{
delete[] _str;
_str = nullptr;
}
void Print()
{
cout << _str << endl;
}
};
String func(const char* str)
{
String temp(str);
return temp;//返回的是temp的临时拷贝对象--将亡值
}

移动拷贝和移动赋值的本质是:将传递的临时对象参数的资源窃取交换变为自己的,通过这种方式,可以在极其高效的前提下达到深拷贝的效果。其中在STL的一些容器中也加入了移动拷贝和移动赋值的接口函数,如list、vector等容器都有。

3.3模版中的万能引用&&

在模板中,可以使用带右值引用相同的引用符号&&来表示万能引用。

需要注意的是在模板中的&&符号并不代表着右值引用,其表示为万能引用,意思就是既能表示为左值引用又能表示为右值引用,而其引用的类型则取决于其接受的参数值类型,左值则使用左值引用,同理反之为右引用。

3.31 完美转发问题

void Fun(int& x) 
{ 
cout << "左值引用" << endl;
}
void Fun(const int& x) 
{ 
cout << "const 左值引用" << endl; 
}
void Fun(int&& x)
{
cout << "右值引用" << endl; 
}
void Fun(const int&& x)
{ 
cout << "const 右值引用" << endl;
}
template<class T>
void PerfectForward(T&& t)
{
Fun(t);
}

在模板中的万能引用中实际存在着一个问题, 虽然其本身能够接受使用左值、右值两种不同的引用方式,但可能会由于二次传发参数的原因导致参数的性质发生变为,如左值变为右值。

为了解决由于模板中的万能引用&&由于二次传参,导致参数左右值类型发生变化的问题,我们可以使用:forward<类模板>(参数)的形式来保持参数第一次传参时的左右值类型,完成完美转发。

template<class T>
void PerfectForward(T&& t)
{
    Fun(forward<T>(t));//保持参数t的原生类型属性
}


原文地址:https://blog.csdn.net/Bit_qnw/article/details/142854234

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!