IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 一文读懂什么是C++移动语义《一》 -> 正文阅读

[C++知识库]一文读懂什么是C++移动语义《一》

C++ 右值引用

block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38

右值的引入

作为在C++11中引入的一个类型,容易引起误解的是,右值引用并没有说明引入是为了什么,是为了解决什么问题。

右值引用可以解决以下问题

  1. 实现移动语义
  2. 完美转发

左值和右值来自原先的C语言,左值可以出现在赋值左边或者右边,而右值只能出现在赋值的右边

int a = 42;
int b = 43;

// a and b are both l-values:
a = b; // ok
b = a; // ok
a = a * b; // ok

// a * b is an rvalue:
int c = a * b; // ok, rvalue on right hand side of assignment
a * b = 42; // error, rvalue on left hand side of assignment

在 C++ 中,这作为第一个直观的左值和右值方法仍然很有用。但是,带有用户定义类型的 C++ 引入了一些关于可修改性和可分配性的微妙之处,导致此定义不正确。我们没有必要进一步讨论这个问题。这是一个替代定义,尽管它仍然存在争议,但它将使您能够处理右值引用:左值是一个引用内存位置的表达式,并允许我们通过&操作符取得地址,右值,不是左值的都是右值。

// lvalues:
//
int i = 42;
i = 43; // ok, i is an lvalue
int* p = &i; // ok, i is an lvalue
int& foo();
foo() = 42; // ok, foo() is an lvalue
int* p1 = &foo(); // ok, foo() is an lvalue

// rvalues:
//
int foobar();
int j = 0;
j = foobar(); // ok, foobar() is an rvalue
int* p2 = &foobar(); // error, cannot take the address of an rvalue
j = 42; // ok, 42 is an rvalue

移动语义

假设有一个类X,类中的成员变量m_pResource是一个需要花费时间和内存取进行构造和析构的类型,比如m_pResource是一个vector类型,对其进行赋值时将会产生大量的析构和构造函数的调用。

X& X::operator=(X const & rhs)
{
  // [...]
  // Make a clone of what rhs.m_pResource refers to.
  // Destruct the resource that m_pResource refers to. 
  // Attach the clone to m_pResource.
  // [...]
}

同样的问题会出现在copy构造函数上

X foo();
X x;
// perhaps use x in various ways
x = foo();
  • clones the resource from the temporary returned by foo,

  • destructs the resource held by x and replaces it with the clone,

  • destructs the temporary and thereby releases its resource.

当赋值操作符的右边是右值的话,只是交换值的指针是比较高效的

// [...]
// swap m_pResource and rhs.m_pResource
// [...]

上述这种操作就是移动语义,可以通过操作符重载实现

X& X::operator=(<mystery type> rhs)
{
  // [...]
  // swap this->m_pResource and rhs.m_pResource
  // [...]  
}

以上调用无论是赋值还是copy构造函数,都会导致大量的构造函数和析构函数调用(如当vector中存储很多的类对象时),因此我们当然希望能够实现对传入类型的引用,从而避免这些构造函数和析构函数的调用

block://6984620384730546178?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38

右值引用

如果X是一个类型,那么X&& 就是对X类型的右值引用,为了更好的区分X&被称为左值引用

一个右值引用类型很多地方表现与左值引用相同,除了一些例外。最重要的一条就是,当进行函数重载的时候,左值当成参数传入函数,偏向调用左值引用的函数;当右值传入函数时,更加偏向调用右值重载的函数

void foo(X& x); // 左值函数重载
void foo(X&& x); // 右值函数重载

X x;
X foobar();

foo(x); // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

Rvalue references allow a function to branch at compile time (via overload resolution) on the condition “Am I being called on an lvalue or an rvalue?”

大体意思就是,右值引用允许编译器期间通过是右值还是左值调用不同的函数

当然你可以使用上述方法重载任何函数,就像上述所示。但是通常会被用于重载拷贝构造函数和赋值构造函数,用来实现移动语义

X& X::operator=(X const & rhs); // classical implementationX& X::operator=(X&& rhs)
{
  // Move semantics: exchange content between this and rhs
  return *this;
}
Note: If you implement
void foo(X&);
but not
void foo(X&&);
then of course the behavior is unchanged: foo can be called on l-values, but not on r-values. If you implement
void foo(X const &);
but not
void foo(X&&);
then again, the behavior is unchanged: foo can be called on l-values and r-values, but it is not possible to make it distinguish between l-values and r-values. That is possible only by implementing
void foo(X&&);
as well. Finally, if you implement
void foo(X&&);
but neither one of
void foo(X&);
and
void foo(X const &);
then, according to the final version of C++11, foo can be called on r-values, but trying to call it on an l-value will trigger a compile error.

强制移动语义

我们都知道,在给予更多控制权和避免粗心大意犯错方面C++选择给予更多的控制权,你不但可以在右值上实现移动语义,而且你可以自行决定在左值上实现移动语义,一个很好的例子就是std::swap函数

template<class T>
void swap(T& a, T& b) 
{ 
  T tmp(a);
  a = b; 
  b = tmp; 
} 

X a, b;
swap(a, b);

这里没有使用右值,因此有没有实现移动语义,但是我们知道实现移动语义会更好,只要变量作为复制构造或者赋值的源出现,该变量要么根本就不再使用,要么就作为赋值的目标。

C++11中与一个被调用的库函数std::move可以将其参数转换 右值, 不做其他事情

void swap(T& a, T& b) 
{ 
  T tmp(std::move(a));
  a = std::move(b); 
  b = std::move(tmp);
} 

X a, b;
swap(a, b);

修改之后上述三行实现了移动语义,需要注意的是,对于那些没有实现移动语义的类型(即:没有使用右值引用版本重载它们的复制构造函数和赋值运算符),对于这些类型新的swap就和旧的一样

既然、知道了移动语义std::move,如下:

a = b;

你期望在这里发生什么?你期望a持有的对象被b的复制出来的副本替换,并且希望a先前持有的对象析构,现在我们考虑一下语义:

a = std::move(b); 

如果实现了移动语义,会交换a和b持有的对象,不会有任何对象进行析构。当然结束之后a原先持有的对象的生命周期将和b的作用范围绑定,b超出范围a原先持有的对象将会被销毁。

所以从某种意义上说,我们在这里陷入了非确定性破坏的阴暗世界:一个变量已被分配,但该变量以前持有的对象仍在某处。只要该对象的销毁不会产生任何外界可见的副作用,就可以了。但有时析构函数确实有这样的副作用。一个例子是释放析构函数内的锁。因此,具有副作用的对象销毁的任何部分都应该在复制赋值运算符的右值引用重载中显式执行:

X& X::operator=(X&& rhs)
{

  // Perform a cleanup that takes care of at least those parts of the
  // destructor that have side effects. Be sure to leave the object
  // in a destructible and assignable state.

  // Move semantics: exchange content between this and rhs
  
  return *this;
}

右值引用就是右值吗?

像以前一样,我们为X实现复制构造函数和赋值操作符重载来实现移动语义。

假如:

void foo(X&& x)
{
  X anotherX = x; //  调用右值引用赋值重载函数还是左值???
  // ...
}

代码中函数内x是一个左值引用,然而我们期望让右值引用就是本身就是右值。右值引用的设计者提供了一个更好的思路:

Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.

大意就是,右值引用可以是左值也可以是右值,评判的标准是,如果这个值有命名就是左值,如果没有就是右值。

那么上述代码中,虽然参数传进的是右值,但是进入函数的时候,因为x已经有命名了,所以函数内部的x是左值,那么函数内部调用的也是左值的赋值函数

void foo(X&& x)
{
  X anotherX = x; // calls X(X const & rhs)
}

如下是一个没有名字的右值,因此会调用右值赋值函数

X&& goo();
X x = goo(); // calls X(X&& rhs) because the thing on
             // the right hand side has no name

这种设计的背后思路就是:允许移动语义应用于一些有名字的对象

X anotherX = x;
  // x is still in scope!

以上语句是非常危险的,移动的食物应该在移动后立即死亡并消失,因此有一条规则,如果它有一个名字,那么它就是左值

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-16 11:05:17  更:2021-07-16 11:05:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 4:03:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码