C++11:移动语义

简介: C++11:移动语义

1、产生背景

背景:在程序运行的过程中,会产生大量的临时对象。临时对象只在某一个表达式执行的过程中创建,表达式执行完毕时,临时对象马上就被销毁。其作用是用来进行过渡的,造成了资源拷贝的不必要的浪费。

需求:希望将临时对象直接转移到新对象中

问题:临时对象是右值,只能通过 const 引用来绑定,但 const 引用同时又可以绑定到左值。C++11之前,当传递参数时,const 引用是无法判断传递过来的参数是左值还是右值(所以const引用又称万能引用)。需要在语法层能够唯一确定传递过来的参数是右值。

解决:C++11 引入右值引用来优化性能,具体来说通过移动语义避免临时对象拷贝,通过 move 语义将临时对象转移到其他对象中,通过 forward 完美转发来解决不能按照参数实际类型来转发的问题。

2、右值引用

左值 vs 右值

  • 左值:表示对象的身份,可以取地址,持久的状态。
  • 右值:表示对象的值,不能取地址,例如:临时对象、字面常量都是右值

右值引用:只能绑定到右值的引用,指向将要被销毁的对象,也就意味着可以自由地接管所引用对象的资源。

变量是左值,不能将一个右值引用绑定到一个变量上,即使这个变量是是右值引用类型。

int &&rr1 = 42;  // 正确,字面值常量是右值
 int &&rr2 = rr1; // 错误,表达式 rr1 是左值,右值引用变量仍然是个左值。

3、std::move

添加编译选项: -fno-elide-constructors

唯一功能:强制将左值转换为右值,等同于 static_cast<T &&>(lvaule),没有性能上的提升。将左值转换为右值后,左值就不能直接使用了。

std::move()作用于内置类型没有任何作用,内置类型本身是左值还是右值,经过移动后不会改变,对于自定义类型效果不错。

4、移动语义函数

移动语义可以将资源通过浅拷贝方式从一个对象转移到另一个对象,只是转移,没有内存拷贝,从而减少临时对象的创建、拷贝和销毁。传递右值,优先调用移动语义的函数,即移动语义函数的执行优先于复制控制语义函数的执行。

移动语义

移动操作通常不抛出异常,若不抛出异常,则必须将其标记为 noexcept,显式告诉标准库可以安全使用。移动操作后,移动后源对象必须保持有效的、可析构的状态,但用户不能对其值做出任何假设。

只有当一个类没有定义任何版本的拷贝控制成员,且所有数据成员都能移动构造或移动赋值时,编译器才会为它合成移动构造函数或移动赋值运算符。

移动构造函数

  • 浅拷贝
  • 转移后将其置为 nullptr
// 移动构造函数:右值引用作为函数参数
String(String &&rhs) noexcept 
// 1、浅拷贝
: _pstr(rhs._pstr) { 
    // 2、移动后,销毁源对象,这里将其设为 nullptr
    rhs._pstr = nullptr;
}

移动赋值运算符函数

  • 自移动
  • 释放左操作数
  • 浅拷贝
  • 返回 this 指针
// 移动赋值运算符函数:右值引用作为函数参数
String &operator=(String &&rhs) noexcept {
    // rhs 右值引用,拥有名字的变量,在函数内部是一个左值
    // 1、自移动
    if(this != &rhs) {
        // 2、释放左操作数
        delete [] _pstr; 
        _pstr = nullptr;
        // 3、浅拷贝
        _pstr = rhs._pstr; 
        rhs._pstr = nullptr;
    }
    // 4、返回*this
    return *this;
}

例:实现内置类型 string 类

#include <string.h>
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
class String {
public:
    friend std::ostream & operator<<(std::ostream &os, const String&);
  String()
  : _pstr(new char[1]())
  { cout << "String()" << endl; }
  String(const char * pstr)
  : _pstr(new char[strlen(pstr) + 1]()) {
      cout << "String(const char*)" << endl;
        strcpy(_pstr, pstr);  
  }
  String(const String & rhs)
  : _pstr(new char[strlen(rhs._pstr) + 1]()) {
      cout << "String(const String &)" << endl;
        strcpy(_pstr, rhs._pstr);
  }
  String & operator=(const String & rhs) {
    cout << "String & operator=(const String&)" << endl;  
    if(this != &rhs) {
      delete [] _pstr;
      _pstr = new char[strlen(rhs._pstr) + 1]();
      strcpy(_pstr, rhs._pstr);
    }
    return *this;
  }
  String(String && rhs) noexcept
  : _pstr(rhs._pstr)  {
         cout << "String(String&&)" << endl;
    rhs._pstr = nullptr;
  }
  String & operator=(String && rhs) noexcept {  
    cout << "String& operator=(String&&)" << endl;
    if(this != &rhs) {
      delete [] _pstr;
      _pstr = rhs._pstr;
      rhs._pstr = nullptr;
    }
    return *this;
  }
  ~String() {
         cout << "~String()" << endl;
    if(_pstr){
      delete [] _pstr;
      _pstr = nullptr;
    }
  }
private:
  char * _pstr;
};
std::ostream & operator<<(std::ostream &os, const String& rhs) {
  os << rhs._pstr;
  return os;
}

5、forward 转发

forward 完美转发:参数在传递过程中保持其值属性的功能,即在参数传递过程中,左值传递后仍是左值,右值仍是右值,根据参数的类型转发给正确的函数。

6、emplace_back

就地构造对象,只调用一次构造函数,避免了内存的拷贝和移动。

push_back(); // 两次构造(临时对象创建,容器内部创建),一次析构(临时对象析构)。
 emplace_back(); // 一次构造(容器内直接创建)
相关文章
|
7月前
|
C++
C++11 右值引用和移动语义(三)
C++11 右值引用和移动语义
25 0
|
5月前
|
编译器 C++ 容器
【C++11特性篇】探究【右值引用(移动语义)】是如何大大提高效率?——对比【拷贝构造&左值引用】
【C++11特性篇】探究【右值引用(移动语义)】是如何大大提高效率?——对比【拷贝构造&左值引用】
|
2月前
|
存储 编译器 C++
【C++】—— C++11新特性之 “右值引用和移动语义”
【C++】—— C++11新特性之 “右值引用和移动语义”
|
7月前
|
存储 安全 编译器
【C++11新特性】右值引用和移动语义(移动构造,移动赋值)
【C++11新特性】右值引用和移动语义(移动构造,移动赋值)
|
4月前
|
存储 编译器
C++11(左值(引用),右值(引用),移动语义,完美转发)
C++11(左值(引用),右值(引用),移动语义,完美转发)
32 0
|
4月前
|
编译器 C++
c++左值、右值引用和移动语义
c++左值、右值引用和移动语义
22 0
c++11新特性——右值引用和move语义
c++11新特性——右值引用和move语义
|
5月前
|
编译器 C++
深入理解 C++ 右值引用和移动语义:全面解析
C++11引入了右值引用,它也是C++11最重要的新特性之一。原因在于它解决了C++的一大历史遗留问题,即消除了很多场景下的不必要的额外开销。即使你的代码中并不直接使用右值引用,也可以通过标准库,间接地从这一特性中收益。为了更好地理解该特性带来的优化,以及帮助我们实现更高效的程序,我们有必要了解一下有关右值引用的意义。
57 0
|
6月前
|
C++ 容器
C++新特性:右值引用,移动语义,完美转发
C++新特性:右值引用,移动语义,完美转发
38 0
|
6月前
|
存储 编译器 C++
【C++杂货铺】一文总结C++11新特性:右值引用 | 移动语义 | 完美转发
【C++杂货铺】一文总结C++11新特性:右值引用 | 移动语义 | 完美转发
34 0