移动语义和引用折叠、完美转发

ericling0529 / 2024-04-24 / 原文

移动构造、移动赋值

C++11新增了移动语义新特性,移动语义允许在不复制数据的情况下转移资源的所有权。在这之前,对象通过拷贝构造函数拷贝赋值运算符进行传递,发生大量的数据复制,导致性能下降。

以常用的string对象为例,

#include <cstring>
#include <iostream>
class string {
public:
  string(const char *p = nullptr) {
    std::cout << "default ctor" << std::endl;
    if (p != nullptr) {
      _data = new char[strlen(p) + 1];
      strcpy(_data, p);
    } else {
      _data = new char[1];
      *_data = '\0';
    }
  }
  ~string() {
    std::cout << "destructor" << std::endl;
    delete[] _data;
    _data = nullptr;
  }
  string(const string &str) {
    std::cout << "copy ctor" << std::endl;
    _data = new char[strlen(str._data) + 1];
    strcpy(_data, str._data);
  }
  string &operator=(const string &str) {
    std::cout << "copy assignment" << std::endl;
    if (this == &str) {
      return *this;
    }
    delete[] _data;
    _data = new char[strlen(str._data) + 1];
    strcpy(_data, str._data);
    return *this;
  }
  string(string &&str) {
    std::cout << "move ctor" << std::endl;
    _data = str._data;
    str._data = nullptr;
  }
  string &operator=(string &&str) {
    std::cout << "move assignment" << std::endl;
    if (this == &str)
      return *this;
    delete[] _data;
    _data = str._data;
    str._data = new char[1];
    str._data[0] = '\0';
    return *this;
  }
  const char *c_str() const { return _data; }

private:
  char *_data;
};
string foo(const string &val) {
  const char *str = val.c_str();
  string tmp(str);
  return tmp;
}
int main() {
  string str1("hello");
  string str2("world");
  str2 = foo(str1);
  std::cout << str2.c_str() << std::endl;
  return 0;
}

如果没有移动语义,上面这段代码中会发生两次拷贝,
第一次是foo函数的返回,会发生一次拷贝构造main函数栈帧上的临时对象(开辟内存,拷贝数据),然后析构tmp(释放内存)。
第二次是str2的拷贝赋值,将main函数栈帧上的临时对象拷贝赋值给str2,又是开辟内存,拷贝数据,然后析构临时对象。

因此没有移动语义,对象的传递将会发生大量的拷贝,尤其是各种临时对象的返回以及临时对象的赋值运算重载。

在增加移动语义后,由tmp构造main函数栈帧临时对象时,不用开辟新的内存,而是直接把tmp中的char*移动到临时对象中,这种资源的所有权转移,避免了内存的开辟释放,以及数据拷贝。同理,main函数栈帧临时对象赋值给str2时,也是直接转移char*即可。

这便是移动语义的好处:减少不必要的内存开辟和释放以及数据拷贝。

std::move()的使用