iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++移动语义详细介绍使用
  • 545
分享到

C++移动语义详细介绍使用

C++移动语义C++移动语义用法 2023-01-28 06:01:49 545人浏览 安东尼
摘要

目录1.移动构造函数2.右值引用3.std::move()将左值强制转换为右值引用4.拷贝语义和移动语义1.移动构造函数 移动语义就是使用移动构造函数来构造对象。 我们知道在类中如果

1.移动构造函数

移动语义就是使用移动构造函数来构造对象。

我们知道在类中如果存在指针数据成员,那么我们就一定要写拷贝构造函数,进行深拷贝

如下所示,就是拷贝构造函数的用法:

#include<iOStream>
using namespace std;
class A
{
    public:
        int * ptr;
        A():ptr(new int(0))
        {
            cout<<"constructor\n";
        };
        A(const A & h):ptr(new int(*h.ptr))//拷贝构造函数
        {
            cout<<"copy constructor\n";
            //deep copy
        };
        ~A()
        {
            cout<<"destructor\n";
            delete ptr;
        };
};
A getA()
{
    return A();
}
int main()
{
    A a=getA();
}
//g++ .\test.cpp -std=c++11 -fno-elide-constructors

constructor
copy constructor
destructor
copy constructor
destructor
destructor

可以知道上面代码中,实际上产生了3个对象,在getA()函数中,使用默认构造函数产生一个对象,然后将其作为返回值时,又会通过拷贝构造函数产生一个对象,然后在main()函数中,又会通过拷贝构造函数构造出对象a,所以总共有3个对象产生,我们这里的拷贝构造函数是进行的深拷贝,所以就会开辟3块内存.

在C++11中,我们可以使用移动构造函数,对上述代码进行优化

#include<iostream>
using namespace std;
class A
{
    public:
        int * ptr;
        A():ptr(new int(0))
        {
            cout<<"constructor\n";
        };
        A(const A & h):ptr(new int(*h.ptr))//拷贝构造函数
        {
            cout<<"copy constructor\n";
            //deep copy
        };
        A(A && h):ptr(h.ptr)//移动构造函数
        {
            h.ptr=nullptr;
            cout<<"move constructor\n";
        }
        ~A()
        {
            cout<<"destructor\n";
            delete ptr;
        };
};
A getA()
{
    return A();
}
int main()
{
    A a=getA();
}
//g++ .\test.cpp -std=c++11 -fno-elide-constructors

constructor
move constructor
destructor
move constructor
destructor
destructor

移动构造函数,它是进行的浅拷贝,由于被移动的值会立即进行析构,所以我们不关心它,只需进行浅拷贝,将其开辟的内存空间转让给别人。上述代码中,也会构造出3个对象,但是它们只开辟一块内存空间,这就是移动构造函数的优势。

总之,我们发现移动构造函数和拷贝构造函数的区别,其实就是深拷贝和浅拷贝的区别,移动构造函数的开销更小,当然我们关心的是,移动构造函数何时会被触发?在上面代码中就是一个例子,将getA()中的局部匿名对象移动给返回值,然后将返回值移动给 main()中的a

这里我们给出结论:移动构造函数只有在使用右值或右值引用来构造对象时才会调用

那么什么是右值?

getA()中的A()就是右值,getA()的返回值也是右值,所以用它们构造对象时,会调用移动构造函数

那么什么是右值引用?

顾名思义就是右值的引用

2.右值引用

在C++11中,我们将值划分为:左值、右值(分为将亡值和纯右值)

左值:可以取地址,有名字的值

右值:不能取地址,没有名字的值

纯右值:运算表达式,如1+2,或者和对象无关的字面值,如true,或者非引用的函数返回值,或者lambda表达式

将亡值:仅和右值引用相关的值,它包括:右值引用的函数返回值T&&,或者std::move的返回值,或者被转换为T&&类型的函数返回值

注意:不管是纯右值还是将亡值,它们的存活时间都很短。不要被将亡值的名称所迷惑了,其实所以右值的都会即将消亡。

实际上,对于纯右值和将亡值的定义很难给出,而且我们也不需要区分它们两,但是,我们至少可以确定一个值是左值还是右值。

C++98中所提及的引用,在C++11中我们称之为左值引用,即这个引用只能绑定左值,在C++11中我们提供了一种新的能够绑定右值的引用,即右值引用。

我们知道左值引用实际是一个变量的别名,右值引用它实际是一个匿名变量的别名

#include<iostream>
using namespace std;
class A
{
    public:
        int * ptr;
        A():ptr(new int(0))
        {
            cout<<"constructor\n";
        };
        A(const A & h):ptr(new int(*h.ptr))//拷贝构造函数
        {
            cout<<"copy constructor\n";
            //deep copy
        };
        A(A && h):ptr(h.ptr)//移动构造函数
        {
            h.ptr=nullptr;
            cout<<"move constructor\n";
        }
        ~A()
        {
            cout<<"destructor\n";
            delete ptr;
        };
};
A getA()
{
    return A();
}
int main()
{
    A&& a=getA();//右值引用
}

constructor
move constructor
destructor
destructor

在上述代码中getA()的返回值是一个右值,它是一个临时值,如果我们写成A a=getA();,那么这个临时值给a进行移动构造后就会立即被析构,而如果我们使用A&& a=getA();,那就意味著我们给这个临时值进行续命,a就是这个临时值的别名,所以上述代码就会少一个对象的构造。

总之,右值引用就是一种绑定右值的引用,实际上在C++98中,我们所知的const T &,这样的引用,也可以绑定右值,他也叫做万能引用,当他绑定右值的时候它的作用和右值引用是一样的,只不过这里的const是底层的,所以我们不能用其修改右值,所以右值引用绑定右值时,可以修改该右值,而当万能引用绑定右值时,我们不可以修改该右值

T& a;//左值引用,只能绑定非常量左值
T&& a;//右值引用,只能绑定非常量右值
const T& a;//万能引用,它可以绑定一切值,但是它不能修改该值
const T&& a;//和万能引用功能一样(一般不使用)

我们仔细来思索一下右值引用的用处,从本质上讲,它是给右值进行续命,而从实践上讲,它就是用来移动语义的,但是移动语义的时候,我们希望修改原来的右值(看上面代码中的移动构造函数,它实际上修改了右值),所以我们说const T&&这种是无用的,

我们在学习了C++11中的移动语义和右值引用知识后,我们要深知一个编程规矩:

只要类中有指针数据成员,就一定要重写拷贝构造函数和移动构造函数

3.std::move()将左值强制转换为右值引用

看一下下面这段代码

#include<iostream>
using namespace std;
class A
{
    public:
        int * ptr;
        A():ptr(new int(0))
        {
            cout<<"constructor\n";
        };
        A(const A & h):ptr(new int(*h.ptr))//拷贝构造函数
        {
            cout<<"copy constructor\n";
            //deep copy
        };
        A(A && h):ptr(h.ptr)//移动构造函数
        {
            h.ptr=nullptr;
            cout<<"move constructor\n";
        }
        ~A()
        {
            cout<<"destructor\n";
            delete ptr;
        };
};
A&& getA()
{
    return std::move(A());
}
int main()
{
    A&& a=getA();
}

constructor
destructor

getA()中的A()是右值,为什么还要用std::move将其转换为右值引用?因为A()是一个纯右值,右值引用当然可以绑定纯右值,但是A()是一个局部对象,在函数中返回引用时,我们禁止返回局部对象的引用,但是当我们使用std::move后,A()就会转换为右值引用类型,这样子就可以将其作为引用返回。这是一种返回局部对象引用的特殊方法。

注意,这是一个涉及原则的问题,匿名对象是纯右值

class  A
{};
int main()
{
    A& a=A();//报错,左值引用无法绑定纯右值
}

实际上,std::move()等价于static_cast<T&&>(lvalue),即将左值转换为右值引用。

但是,std::move()有一个bug,即被转化为右值引用的左值,不会被立即析构。

#include<iostream>
using namespace std;
class A
{
    public:
        int* ptr;
        A():ptr(new int(999)){}
        ~A(){delete ptr;}
        A(const A& h):ptr(new int(*h.ptr)){}
        A(A&& h):ptr(h.ptr)
        {
            h.ptr=nullptr;
        }
};
int main()
{
    A a;
    A b(std::move(a));
    cout<<*a.ptr<<endl;//报错
}

上述代码就会报错,因为a被转化为右值引用后,b会调用移动构造函数来构造它自己,而在移动构造函数中,它将a.ptr置空

#include<utility>
class A
{
    public:
        int *ptr;
        A():ptr(new int(0)){}
        ~A(){delete ptr;}
        A(const A& h):ptr(new int(*h.ptr)){}
        A(A&& h):ptr(h.ptr){h.ptr=nullptr;}
};
class B
{
    public:
        int *ptr;
        A elem;
        B():ptr(new int(0)){}
        ~B(){delete ptr;}
        B(const B&h):ptr(new int(*h.ptr)),elem(h.elem){}
        B(B&& h):ptr(h.ptr),elem(std::move(h.elem)){h.ptr=nullptr;}
};

注意看,B(const B&h):ptr(new int(*h.ptr)),elem(h.elem){}中对elem的初始化使用的是A的拷贝构造函数,

B(B&& h):ptr(h.ptr),elem(std::move(h.elem)){h.ptr=nullptr;}中对elem的初始化使用的是是A的移动构造函数. 注意一点,即使这里我们忘记写std::move()也并无大碍,它会自行调用拷贝构造函数,当然这也会导致一些开销,所以在做类开发的时候,在写类的移动构造函数的时候,总是要记得将类成员move成右值引用。

4.拷贝语义和移动语义

如果一个类支持拷贝构造函数和拷贝赋值函数,那么我们就称该类具有拷贝语义;同样的如果一个类支持移动构造函数和移动赋值函数,那么我们就称该类具有移动语义。

当然有些类是同时支持移动语义和拷贝语义的。

在C++98中的类基本都是只具有拷贝语义的,而在C++11中的基本所有类都支持移动语义,特别的,有些类只支持移动语义,而不支持拷贝语义,这种类,我们称之为资源型类,即资源只能被移动而不能被拷贝,例如智能指针类unique_ptr,文件流ifstream等都是资源型类,在C++11中,我们可以通过一些工具来判断一个类是否支持移动语义。

我们看一下下面的代码

template <class T>
void swap(T& a, T& b)
{
    T tmp(move(a));
    a=move(b);
    b=move(tmp);
}

上述代码中,如果T支持移动语义,那么它就会调用移动构造函数和移动赋值函数,而如果T只支持拷贝语义,那么它也可以调用拷贝构造函数和拷贝赋值函数

我们关于移动语义的另一个话题是:异常。因为如果移动语义没有完成,却抛出异常,那么可能会导致产生悬挂指针。所以在C++11中我们同样有std::move_if_noexcept()函数来检测,移动构造函数是否用noexcept修饰。

再讨论一个关于编译器优化的问题,如今c++编译器已经非常优化了,RVO机制,即所谓返回值优化机制,他能帮你完成类似移动语义的智能优化,但是要记住,编译器优化不是完全奏效的,最好还是自己提高代码效率。

到此这篇关于C++移动语义详细介绍使用的文章就介绍到这了,更多相关C++移动语义内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++移动语义详细介绍使用

本文链接: https://www.lsjlt.com/news/178409.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++移动语义详细介绍使用
    目录1.移动构造函数2.右值引用3.std::move()将左值强制转换为右值引用4.拷贝语义和移动语义1.移动构造函数 移动语义就是使用移动构造函数来构造对象。 我们知道在类中如果...
    99+
    2023-01-28
    C++移动语义 C++移动语义用法
  • C++movesemantic移动语义介绍
    目录前言移动构造为什么我们需要move semantic前言 在说移动语义之前 本文作者假设你已经具备了深拷贝浅拷贝左值右值等基本概念 本文不会再过多叙述 那么接下来 让我们开始吧 ...
    99+
    2024-04-02
  • C++移动语义介绍与使用讲解
    目录引入移动语义std::move引入移动语义 为了能够理解移动语义的目的,我们先从整成的一个类进行示范,示例如下: class TestClass { public: Te...
    99+
    2024-04-02
  • C++BoostMultiIndex使用详细介绍
    目录一、关于BOOST的容器二、Boost.MultiIndex练习一、关于BOOST的容器 容器是 C++ 中最有用的数据结构之一。标准库提供了许多容器,而 Boost 库提供的更...
    99+
    2022-11-13
    C++ Boost MultiIndex C++ MultiIndex
  • C语言指针详细介绍
    本篇内容主要讲解“C语言指针详细介绍”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言指针详细介绍”吧!指针对于C来说太重要。然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机...
    99+
    2023-06-15
  • C语言数组详细介绍
    目录什么是数组一维数组二维数组数组越界 数组名 结尾什么是数组 数组(Array)是一种用来存储同一种类型的集合,是一种有序的线性结构表。并且数组元素的地址是连续...
    99+
    2024-04-02
  • C++四种cast使用详细介绍
    目录一、static_cast1、基本数据类型转换2、指针和void指针的转换 3、父类和子类之间的转换二、dynamic_cast三、const_cast1、加上cons...
    99+
    2024-04-02
  • C语言指针的详细介绍
    这篇文章主要介绍“C语言指针的详细介绍”,在日常操作中,相信很多人在C语言指针的详细介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C语言指针的详细介绍”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!目录...
    99+
    2023-06-20
  • C++命名空间使用详细介绍
    目录1.前言2.定义3.using 指令4.using 声明5.嵌套的命名空间1.前言 在c++中,为了避免代码名称上所产生冲突,引入了命名空间这个东西。 命名空间相当于划分出一定的...
    99+
    2024-04-02
  • C语言之循环语句详细介绍
    目录前言while语句do...while语句for语句结语前言 C语言中的循环结构是程序中的一个基本结构。 循环结构可以使我们写很少的语句,让计算机反复执行某一过程。 C语言提供了...
    99+
    2024-04-02
  • C++的移动语义怎么使用
    这篇文章主要介绍“C++的移动语义怎么使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“C++的移动语义怎么使用”文章能帮助大家解决问题。什么是移动语义移动语义是 C++11 中新增的一个语言特性,...
    99+
    2023-07-06
  • C++继承详细介绍
    在我们进行开发的时候,我们经常会遇到抽象出来的类之间具有继承关系。 举个简单的例子,比如我们在设计某游戏,当中需要定义Human也就是人这个类。每个人有名字,以及一定的血量,能够工作...
    99+
    2024-04-02
  • Vue自定义指令的使用详细介绍
    目录1. 概述2. 钩子函数3. 自定义全局指令4. 自定义局部指令5. 使用自定义指令实现权限管理6. 使用自定义指令实现表单验证1. 概述 除了核心功能默认内置的指令,Vue也允...
    99+
    2024-04-02
  • C++中cout的格式使用详细介绍
    1.cout和i/i++/++i的组合使用 i++ 和 ++i 是有着不同的含义,和 cout 组合使用也会得到不同的结果,下面给出一段代码: #include <iost...
    99+
    2024-04-02
  • C语言初阶之数组详细介绍
    目录插入排序讲解二维数组二维数组的初始化二维数组的访问n维数组字符数组字符数组和字符串字符数组的输入输出字符串函数的简单使用综合使用字符串函数总结插入排序讲解 #include&...
    99+
    2024-04-02
  • C#属性的详细介绍
    这篇文章主要讲解了“C#属性的详细介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#属性的详细介绍”吧!C# 属性示例代码class TimePeriod  ...
    99+
    2023-06-17
  • C++超详细介绍模板
    目录定义例子格式处理方法定义 函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。 ...
    99+
    2024-04-02
  • C++模板超详细介绍
    目录1.前言2.函数模板3.类模板1.前言 模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。 模板是创建泛型类或函数的蓝图或公式。 通常有两种形式:函数模板和...
    99+
    2024-04-02
  • CSS3动画的详细介绍
    这篇文章主要介绍“CSS3动画的详细介绍”,在日常操作中,相信很多人在CSS3动画的详细介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”CSS3动画的详细介绍”的疑惑有所帮...
    99+
    2024-04-02
  • SQL语句 - 多表查询使用详细介绍
    文章目录 多表查询多表查询简介多表查询内连接多表查询外连接多表查询子查询 多表查询 多表查询简介 例如我们有一张员工表和部门表, 员工表有6条数据, 部门表表有4条数据: # 创建部门...
    99+
    2023-09-01
    sql 数据库 java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作