跳转至

值类型与右值引用

约 603 个字 56 行代码 预计阅读时间 3 分钟

下面函数执行几次拷贝?

int get_x() {
  int a = 10;
  return a;
}
int x = get_x();

执行了两次拷贝,即 int tmp = a; int x = temp;,因此,如果对于类似大内存池进行拷贝构造时,需要进行两次copy,就十分低效

C++98的表达式类型

  • 左值:指向特定内存的具名对象
  • 右值:临时对象,字符串除外的字面量

判断一个表达式是左值还是右值:是否可以取地址

  // &get_x(); // get_x()返回值是临时对象,是右值,不能取地址
  int y = 10;
  // int *p1 = &y++; // y++ 返回的是临时对象,是右值,错误
  int *p2 = &++y;

对于 int *p = &x++;,对于 x++ 可以看为下面这个函数,传入一个引用,返回一个临时值b;因此不可以对其取地址,代码是错的

int fun(int &a) {
  int b = a;
  a = a + 1;
  return b;
}

对于 int *p = &++x;,对于 ++x 可以看为下面这个函数,传入一个引用,返回该引用;因此可以对其取地址,是正确的

int &func(int &a) {
  a = a + 1;
  return a;
}

C++11的表达式类型

expression(表达式):

  • glvalue(泛左值)
    • lvalue(左值)
    • xvalue(将亡值)
  • rvalue(右值)
    • xvalue(将亡值)
    • prvalue(右值)

赋值操作只有拷贝这种唯一解法吗?

  • 拷贝操作
  • 引用操作
  • 移动操作

右值引用与移动语义

左值引用不接受右值,所以出现只接受右值的右值引用 Type &&

移动语义:std::move可以将左值转为右值,左值的生命周期结束,这个右值被称为将亡值

注意:

  • 纯右值也可以 std::move
  • 类中未实现移动构造,std::move之后仍是**拷贝** (移动语义没移动),std::move还是去我们实现的类中寻找函数,当发现没有移动构造时,由于拷贝构造的const &可以接受任何值,所以进行了拷贝构造
  • 右值引用仍是左值
  • 右值绑定到右值引用上连移动构造都不会发生

如何得到将亡值

  • 将泛左值转化为将亡值:
    • static_cast(xxx)
    • std::move(xxx) 移动语义没移动
  • 临时量实质化(c++17引入)
  // 拷贝操作,getPool()进行两次copy,
  // 注意:需要在没有移动构造的前提下,有移动构造时会进行两次移动构造
  BigMemoryPool aaa = getPool();
  BigMemoryPool bbb = aaa;   //拷贝操作
  BigMemoryPool &ccc = aaa;  // 引用操作
  // 右值绑定到右值引用上连移动构造都不会发生,eee只是aaa的一个别名,操作aaa,eee,ccc是一样的
  BigMemoryPool &&eee = std::move(aaa);
  BigMemoryPool ddd = std::move(aaa);  // 移动操作,此时aaa已经销毁了

  int x = get_x();
  // int &z = 10;  // &z是左值引用,字面量10是右值,左值引用不接受右值
  // int &z = get_x();  // get_x()返回临时对象,错误同上
  int &&zz = 10;  // &&z是右值引用,右值引用接受右值
  // int &&z = x; // 右值引用只能绑定右值,错误
  int &&z = std::move(x);
  &z;  // 右值引用仍是左值
  const int k = 10;
  const int &&kk = std::move(k);  // const &&
BigMemoryPool get_pool(const BigMemoryPool &pool) { return pool; }

BigMemoryPool make_pool() {
  BigMemoryPool pool;
  return get_pool(pool);
}
BigMemoryPool kkk= make_pool();

对于上面这个函数,执行代码时:

如果类中有拷贝构造和移动构造的情况下,会执行一次拷贝两次移动,原因如下:

BigMemoryPool get_pool(const BigMemoryPool &pool) {
  return pool;  // 这里的pool是个左值
}
// BigMemoryPool temp = pool;
// 退出上面这个函数时,会出现这个临时变量,因为pool是左值,这里发生第一个拷贝
BigMemoryPool make_pool() {
  BigMemoryPool pool;  // 调用无参构造函数什么都不发生
  return get_pool(pool);
}
// BigMemoryPool temp2 = temp; // 由于这里temp是临时变量,所以发生移动
// 下面是main函数内
BigMemoryPool kkk = make_pool();
// BigMemoryPool kkk = temp2; 这里发生第二次移动,将temp2赋给kkk

如果类中没有实现移动构造,就会执行三次拷贝

注:上述代码运行时,需要在CMakeLists.txt中加入 set(CMAKE_CXX_FLAGS -fno-elide-constructors) #禁止返回值优化 ,否则,执行代码时,不管写不写移动构造,都只会发生一次拷贝,编译器会帮我们做返回值优化

颜色主题调整

评论区~

有用的话请给我个赞和 star => GitHub stars
快来跟我聊天~