前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++11学习笔记3

C++11学习笔记3

作者头像
mythsman
发布2022-11-14 16:12:05
2750
发布2022-11-14 16:12:05
举报
文章被收录于专栏:mythsman的个人博客

右值引用

分类

在古老的标准里,C++中的变量分为左值(lvalue)与右值(rvalue)这两种,左值就是能够用&获得地址的值,可以对他进行修改,右值就是不能用&获得地址的值,通常只是临时变量,不能进行修改。而在C++11中,变量不再仅仅分为左值与右值了,他引入了另一种值叫将亡值(expire value,xvalue)。从此,变量类型分为了三种:

  • lvalue  :left value 就是传统意义上的左值
  • xvalue  :expire value 就是将亡值
  • prvalue :pure right value 就是传统意义上的右值

而且,xvalue+prvalue又称为rvalue,lvalue+xvalue又称为glvalue。

定义

那么什么是xvalue呢,什么又是右值引用呢? xvalue其实就是对右值的引用:

代码语言:javascript
复制
int &&x=10;

这个x跟传统的左值引用肯定是不一样的,毕竟左值引用是不能引用右值的。 这个x跟左值变量也是不一样的,虽然看上去没啥区别,但是实际上这个x并没有进行构造,而是像左值引用一样,对右值10进行了引用,使得这个右值的内存不被立即释放。这样我们就可以像使用左值一样的使用这个右值了。 那么现在就应该清楚了,右值引用就是通过对右值进行引用使得我们能够保存这个右值的生命周期,并像使用左值一样的使用右值的方法。(自己的定义)

用途

这么费劲心机定义一个右值引用有啥意义呢?其实主要是为了提高变量传递的效率。虽然很多情况下,我们的编译器会做一些优化,但是不同编译器不同,因此下面基于g++的测试我开启了-fno-elide-constructors参数防止其优化。 比如下面的这个过程:

代码语言:javascript
复制
#include<iostream>
class Test{
public:
    Test(){
        std::cout<<"construct"<<std::endl;
    }
    Test(const Test &test){
        std::cout<<"copy"<<std::endl;
    }
    ~Test(){
        std::cout<<"destroy"<<std::endl;
    }
};
int main(){
    Test t=Test();
}

看上去没什么额外开销,而事实上,这个过程的执行结果是:

代码语言:javascript
复制
construct
copy
destroy
destroy

也就是说他执行了一次构造,一次拷贝构造。而事实上,这个拷贝构造是浪费额外开销的,而且这个拷贝其实是对一个右值的拷贝,在拷贝后这个右值就被析构了,我们完全可以不执行析构而让新的值就用这个右值的引用。 旧的做法是使用常量引用来做这件事:

代码语言:javascript
复制
int main(){
    const Test& t=Test();
}

这避免了额外的拷贝,但是后果是这个值只能是常量,无法被修改。 如果使用了右值引用,那么这个问题就简单多了:

代码语言:javascript
复制
int main(){
    Test&& t=Test();
}

结果:

代码语言:javascript
复制
construct
destroy

这个右值引用既没有对结果进行拷贝,也能够让我们像使用一个左值一样的使用他。

移动拷贝构造

为什么引入移动拷贝构造,这是因为我们考虑到了以下问题: 我们知道,构造函数分为深拷贝和浅拷贝,默认的是浅拷贝,浅拷贝导致的结果就是拷贝出来的对象跟拷贝前的对象拥有相同的堆内元素,如果我们析构了拷贝前的对象,那么拷贝后的对象就无法使用了,因此浅拷贝不适合做赋值的移动操作;而深拷贝呢,又太浪费空间了,完全没有必要生成一个一模一样的对象然后把原先的再删除。因此就引入了拷贝构造这个东西。 比如下面的例子:

代码语言:javascript
复制
class Test{
private:
    int *ptr;
public:
    Test():ptr(new int(0)){
        std::cout<<"construct"<<std::endl;
    }
    Test(const Test &test):ptr(new int(*test.ptr)){
        std::cout<<"copy"<<std::endl;
    }
    Test(Test &&test):ptr(test.ptr){
        test.ptr=nullptr;
        std::cout<<"move"<<std::endl;
    }
    ~Test(){
        delete ptr;
        std::cout<<"destroy"<<std::endl;
    }
};
int main(){
    Test t=Test();
}

结果:

代码语言:javascript
复制
construct
move
destroy
destroy

移动构造的参数就是一个右值引用,他做的事是将移动源的所有堆内元素指针断开,并连上移动目的地的指针。这样移动源就可以安心的析构而不影响移动目的地的堆内元素。同时也省去了不必要的拷贝开销,效率非常的高。

std::move

移动构造有一个问题,就是他的参数必须是右值,这就带来一个问题,如果上面的main函数变成这样:

代码语言:javascript
复制
int main(){
    Test t1;
    Test t2=t1;
}

结果:

代码语言:javascript
复制
construct
copy
destroy
destroy

由于t1是个左值,那么他会去执行拷贝构造而不会执行移动构造,这显然不是我们像看到的。 为了使左值的移动能够使用移动构造,我们就有了std::move这个东西,他的作用很简单,就是把左值变成右值引用。

代码语言:javascript
复制
int main(){
    Test t1;
    Test t2=std::move(t1);
}  

结果:

代码语言:javascript
复制
construct
move
destroy
destroy

如我所愿。 注意一点就是在使用std::move()之后的对象t1这时候的堆内元素就已经无效了。

std::move()这么好用,显然c++中的模板都支持这个move语义。

std::emplace_back

我们知道了对象的构造,拷贝、移动都是要付出一些代价的,那我们很容易就会想到,当我们使用容器的时候,如果使用拷贝构造,显然效率很低;如果使用移动构造,那么还是会有一些移动的开销,需要执行移动构造函数,能不能有一种方法,直接在容器里执行构造函数,这样就既不用拷贝,也不用移动了呢?答案是当然有,这就是std::emplace_back方法。我们可以比较下面的三种方法:

拷贝插入

代码语言:javascript
复制
int main(){
    std::vector<Test>v;
    Test t;
    v.push_back(t);
}

输出:

代码语言:javascript
复制
construct
copy
destroy
destroy

移动插入

代码语言:javascript
复制
int main(){
    std::vector<Test>v;
    v.push_back(Test());
}

输出:

代码语言:javascript
复制
construct
move
destroy
destroy

emplace_back

代码语言:javascript
复制
int main(){
    std::vector<Test>v;
    v.emplace_back();
}

输出:

代码语言:javascript
复制
construct
destroy

显然,这个emplace_back的效率是最高的。

参考资料

深入应用c++11

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 右值引用
    • 分类
      • 定义
        • 用途
          • 移动拷贝构造
          • std::move
          • std::emplace_back
          • 参考资料
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档