C++11带来的move语义

2023-05-16

C++ 11带来了move语义,可以有效的提高STL的效率,这篇文章写的非常好,可以参考,这里对原文进行翻译,加入我自己的理解

原文:http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html

先看看这个例子:

#include <iostream>
 
using namespace std;
 
vector<int> doubleValues (const vector<int>& v)
{
    vector<int> new_values( v.size() );
    for (auto itr = new_values.begin(), end_itr = new_values.end(); itr != end_itr; ++itr )
    {
        new_values.push_back( 2 * *itr );
    }
    return new_values;
}
 
int main()
{
    vector<int> v;
    for ( int i = 0; i < 100; i++ )
    {
        v.push_back( i );
    }
    v = doubleValues( v );
}
调用函数doubleValues时是有两次复制的,一次是在函数返回时,因为local变量要被回收,所以需要 copy构造一个临时对象来返回,当返回这个临时对象后,又需要一次 copy赋值操作来赋值给v。你可能会说,不返回对象就可以啊,可以返回一个引用或者一个指针,但是要这样做就得分配内存,但是C++的一个设计目标就是尽量少分配内存。上述更糟糕之处在于,返回的临时对象使用完之后是会被销毁掉的。

理论上来说,在内存中我们完全可以把临时对象的指针偷过来,而不去拷贝。还有就是我们为什么不能move呢?C++03说,我不知道哪个对象是临时的,哪个不是临时的,但是C++11却可以做到。

这就是右值引用和move语义要做的事情:move语义可以使你在使用临时对象时避免拷贝,并且可以安全的使用临时对象里的资源。

左值和右值

C++中,左值是指可以使用其地址的表达式,左值提供一种(半)永久的内存,比如:

int a;

a = 1;  //a是一个左值

又如:

intx;
int& getRef () 
{
        returnx;
}


 
getRef() = 4;//getRef()这个表达式也是一个左值,因为其提供的返回值全局变量,是有固定地址的。
右值呢?如果一个表达式的结果在一个临时变量中,那它就是一个右值,例如:

intx;
intgetVal ()
{
    returnx;
}
getVal();


再来看返回对象的情况:

string getName ()
{
    return"Alex";
}


getName();
string nema = getName();
此时,对“Alex”进行隐式类型转换,转换为一个string的临时对象,然后拷贝构造给name变量

上边说到C++11能检测出来临时变量和非临时变量,现在就来看看怎么检测 既然有的表达式返回临时变量,那么如果对表达式重载,那么一个返回临时变量的表达式和返回非临时变量的表达式是否有哪些不同呢?

const string& name = getName(); // ok
string& name = getName(); // NOT ok
为什么第一个ok第二个不ok呢,C++认为不管getName如何实现,如果你返回的是一个非const引用,说明你可能回去修改一个可能会消失(如果是一个临时变量的话)东西;另外,如果是返回一个const引用,确保了临时变量不会很快消失( 不是很懂,不会很快消失,那多久会消失呢)。

C++11中,会让你可以绑定一个mutable右值引用,也就是说,一个值是否临时的,可以使用右值引用来检测,右值引用使用&&语法,可以是const也可以是非const的:

const string&& name = getName(); // ok
string&& name = getName(); // also ok - praise be!
有什么用呢?关于左值引用和右值引用,最重要的是,当你写一个函数,函数的参数是左值引用或者右值引用时,比如:

printReference (constString& str)
{
        cout << str;
}
 
printReference (String&& str)
{
        cout << str;
}


第一个具有const引用的函数,可以接受任意的参数,不管是左值还是右值,不管这个左值或者右值易变或者不易变(if mutable);但是对于第二个函数,除了 mutable rvalue-references类型,其他的类型都可以,比如:

string me( "alex");//mutable rvalue-references类型
printReference(  me ); // calls the first printReference function, taking an lvalue reference

如果是printReference("alex");是不是就调用的第二个函数?

现在,右值引用版本的函数就像一个俱乐部的入口,只有临时变量才可以进入。

现在,既然有方法检测出是否临时变量了,有什么用处呢?

move构造和move赋值操作符
右值引用的一个用处是可以用来创建move构造函数和move赋值操作符,move构造和move赋值可以避免内存重新分配,因为我们已经有了一个临时变量了。
把一个对象中的一个字段移动到另一个对象是什么意思?如果是一个原生类型,比如int,我们赋值就可以了,但是对于指针类型的处理比较有趣。我们不需要再分配和初始化一段内存,我们只需要把临时对象中的指针偷过来,然后让原始指针指向null即可,因为临时对象不再需要了,所以我们可以把它的指针拿过来用:
来看拷贝构造:
class ArrayWrapper
{
    public:
        ArrayWrapper (int n)
            : _p_vals( new int[ n ] )
            , _size( n )
        {}
        // copy constructor
        ArrayWrapper (const ArrayWrapper& other)
            : _p_vals( new int[ other._size  ] )
            , _size( other._size )
        {
            for ( int i = 0; i < _size; ++i )
            {
                _p_vals[ i ] = other._p_vals[ i ];
            }
        }
        ~ArrayWrapper ()
        {
            delete [] _p_vals;
        }
    private:
    int *_p_vals;
    int _size;
};
来看move构造:
class ArrayWrapper
{
public:
    // default constructor produces a moderately sized array
    ArrayWrapper ()
        : _p_vals( new int[ 64 ] )
        , _size( 64 )
    {}
 
    ArrayWrapper (int n)
        : _p_vals( new int[ n ] )
        , _size( n )
    {}
 
    // move constructor
    ArrayWrapper (ArrayWrapper&& other)
        : _p_vals( other._p_vals  )
        , _size( other._size )
    {
        other._p_vals = NULL;
    }
 
    // copy constructor
    ArrayWrapper (const ArrayWrapper& other)
        : _p_vals( new int[ other._size  ] )
        , _size( other._size )
    {
        for ( int i = 0; i < _size; ++i )
        {
            _p_vals[ i ] = other._p_vals[ i ];
        }
    }
    ~ArrayWrapper ()
    {
        delete [] _p_vals;
    }
 
private:
    int *_p_vals;
    int _size;
};

可以看出,move构造和copy构造相互重载了,那么调用的时候如何判断调用哪个构造呢?很简单,上边不是已经有了方法吗,如果是临时对象就会自动调用move构造,如果不是,就会调用copy构造。
那么为什么move构造中要把原始指针置为null呢?因为如果不置为null,那么临时变量消失时,会析构,释放自己的指针,还资源给os,那么新对象中的指针也就随之会被delete掉。所以需要把原始指针置为null,这样临时变量在析构时就找不到给自己分配的那块内存了,那块内存也就可以被新对象正常使用了。
注意:这里的重载规则要求如果要调用move构造,那么一定要传一个临时变量,并且一定要是一个可以修改的临时变量,如果不能修改,那么临对象内的指针就不能置为null了,如果一个函数返回的是const类型,那么就不能调用move构造了

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11带来的move语义 的相关文章

随机推荐

  • 如何安装双系统之ubuntu安装

    如何安装双系统之ubuntu安装 1 首先在Windows下对磁盘分出一块空闲分区大概100G左右 2 然后下载Ubuntu16 04镜像 xff0c 制作启动盘 3 重启电脑 xff0c 按住对应的键 xff08 不同电脑型号可能不同 x
  • 场景分类综述——Remote Sensing Image Scene Classification Meets Deep Learning

    一 场景分类面临的挑战 场景分类的挑战包括 xff1a 1 类内多样性大 xff0c 2 类间相似性高 也称为类间可分性低 xff0c 3 对象 场景尺度的差异大 就类内的多样性而言 xff0c 挑战主要来自于在同一个语义类中出现的地物的巨
  • 配置服务器的磁盘阵列并正确分区

    磁盘阵列 xff0c 即独立磁盘冗余阵列RAID xff08 Redundant Array of Independent Disks xff09 xff0c 其实就是一个将多块独立磁盘结合在一起 xff0c 从而提高数据的可靠性和I O性
  • 软件项目组织架构安排

    这个主题涉及到三个方面 xff0c 项目计划管理 组织管理和技术管理范畴 项目计划管理是项目管理中的一个大篇章 xff0c 包括时间计划 成本计划 费用计划等在内的各类计划管理 xff0c 不是本文章所谈的范围 xff0c 只是本文主题涉及
  • SpringBoot2.x学习(二):为属性注入配置文件中的值:@ConfigurationProperties注解的使用

    文章目录 一 64 ConfigurationProperties 简单介绍二 64 ConfigurationProperties 使用示范1 创建两个 javaBean2 在 SpringBoot 全局配置文件写入需要注入的值2 1 a
  • SpringBoot 2.x学习(三):为属性注入配置文件中的值:@Value 注解的使用

    文章目录 一 64 Value 注解的作用二 使用 64 Value 为普通成员变量注入值1 字面量 xff08 1 xff09 语法 xff08 2 xff09 举例 2 Spring 表达式 xff08 SpEL xff09 xff08
  • 数据结构与算法学习(一):线性表之数组的插入与删除(Java 实现)

    文章目录 一 数组介绍1 线性表2 连续的内存空间和类型相同的数据 二 利用数组实现插入操作及相应的时间复杂度分析1 数组原本有顺序 xff0c 插入后需要继续保持数组有序 xff08 1 xff09 思路分析 xff08 2 xff09
  • 抽象类与接口

    抽象类与接口 接口与抽象类 一 抽象类 说起抽象类 xff0c 我们先说一下如何定义一个抽象方法 span class token keyword abstract span span class token keyword class s
  • SpringBoot 项目集成 mybatis-generator

    SpringBoot 项目集成 mybatis generator mybatis 官方提供了一个插件 xff1a myabtis generator xff0c 可以根据数据库中的表生成对应的实体类和针对单表的一些操作方法 xff0c 可
  • InnoDB 和 MyISAM 的区别

    这里写自定义目录标题 MyISAMINNODB事务支持不支持支持数据行锁定不支持 xff08 表锁 xff09 支持 xff08 行锁 xff09 外键约束不支持支持全文索引支持不支持表空间的大小较小较大 xff0c 约为 2 倍 MyIS
  • xshell上传、下载文件

    安装 lrzsz yum y span class token function install span lrzsz 上传资源到服务器命令 rz 回车后 xff0c 会出现一个弹框 xff0c 选择上传的文件即可 从服务器下载资源命令 s
  • 浅谈vue+webpack项目调试方法

    题外话 xff1a 这几个月用vue写了三个项目了 xff0c 从绊手绊脚开始慢慢熟悉 xff0c 婶婶的感到语言这东西还是得有点框框架架 xff0c 太自由了容易乱搞 xff0c 特别人多的时候 从webpack开始 直接进入正题 有人觉
  • CentOS7下vlan网卡配置

    操作系统 xff1a CentOS 7 x86 64 Everything 1804 开源虚拟软件 xff1a Oracle VM VirtualBox 因为使用VirtualBox默认配置所以网卡设备名是enp0s3 root 64 lo
  • 12、基本数据链路层协议(数据链路层)

    1 基本数据链路层协议 引言 在考察协议之前 xff0c 先明确一下有关底层通信模型的基本假设是有必要的 首先我们假设物理层 数据链路层和网络层都是独立的进程 xff0c 它们通过来回传递信息进行通信 如图所示 xff0c 物理层进程和某些
  • Java 中的上转型和下转型

    在我们的日常中 xff0c 上转型和下转型都使用的比较少 xff0c 所以当别人问起来什么是上转型 xff0c 什么是下转型 xff0c 自己往往一片模糊 xff0c 或者不能将他们进行明显的区分 在这里 xff0c 我将以我个人理解来论述
  • MySQL 中 Join 的基本实现原理

    http isky000 com database mysql join buffer nested loop implement DataBase Dec 3rd 2008 作者 xff1a Sky Jian 可以任意转载 但转载时务必以
  • mysql 常用命令

    1 mysqldump 可以把现有数据库中的表结构以及数据导入到一个文本文件中 mysqldump u root socket 34 socketname 34 p tpch no data gt tpch sql 如果不加上 no dat
  • 完全二叉树学习

    定义 xff1a 假设高度为h xff0c 那么前h 1层都是满的 xff0c 最后一层 xff0c 从左向右 xff0c 连续集中在最左边 xff1b k层的完全二叉树总节点个数最小为2 k 1 xff0c 最大节点个数为2 k 1 可以
  • thrift例程编译报错原因和解决方法总结

    thrift里自带的turoral xff0c 使用make编译时经常会报错 xff0c 总结如下 xff1a 1 如果出现如下错误 xff1a error uint8 t does not name a type error uint32
  • C++11带来的move语义

    C 43 43 11带来了move语义 xff0c 可以有效的提高STL的效率 xff0c 这篇文章写的非常好 xff0c 可以参考 xff0c 这里对原文进行翻译 xff0c 加入我自己的理解 原文 xff1a http www cpro