【C++11】移动语义,完美转发

news2024/11/27 18:39:08

1.移动语义

1.为什么要有移动语义?

C++中有拷贝构造函数和拷贝复制运算符,但是这需要占用一定的空间


class MyClass
{
public:
    MyClass(const std::string& s)
        : str{ s }
    {};
    MyClass(const MyClass& m)
    {
        str=m.str;
    }

private:
    std::string str;
};

int main()
{
    MyClass A{ "hello" };
    MyClass B=A; 
    return 0;
}

这个代码非常的简单就是A对象中有存储字符串hello\0,把A拷贝复制给B,此时会调用拷贝构造函数

但是来看下面的例子

class MyClass
{
public:
    MyClass(const std::string& s)
        : str{ s }
    {};

private:
    std::string str;
};
int main()
{
 std::vector<MyClass> myClasses;
 MyClass tmp{ "hello" };
 myClasses.push_back(tmp);
 myClasses.push_back(tmp);
 return 0;
}

在这个例子中,我们创建了一个容器以及一个MyClass对象tmp,我们将tmp对象添加到容器中2次每次添加时,都会发生一次拷贝操作,但是既然tmp在两次拷贝构造之后就要销毁,那第二次拷贝构函数这个操作是不是有点冗余,要是能有一个函数可以把tmp对象的数据拷贝,但不需要执行拷贝构造函数就好了

2.移动语义

所谓移动语义,就像其字面意思一样,即把数据从一个对象中转移到另一个对象中,从而避免拷贝操作所带来的性能损耗

我们回到上文的例子,对于myClasses容器的第一次push_back,我们期望执行的是拷贝操作,而对于myClasses容器的第二次push_back,由于之后我们不再需要tmp对象了,因此我们期望执行的是移动操作

对于容器的push_back函数来说,它一定针对拷贝操作和移动操作有不同的重载实现,而重载用到的即是左值引用与右值引用。伪代码如下:

   class vector
    {
    public:
        void push_back(const MyClass& value)  // const MyClass& 左值引用
        {
            // 执行拷贝操作
        }

        void push_back(MyClass&& value)  // MyClass&& 右值引用
        {
            // 执行移动操作
        }
    };

std::vector真正做的,是委托具体类型自己去执行拷贝操作与移动操作

class MyClass
    {
    public:
        MyClass(const std::string& s) //拷贝构造
            : str{ s }
        {};
        MyClass(MyClass&& rvalue) //移动构造
            :str{ std::move(rvalue.str) }
        {}

    private:
        std::string str;
    };

3.移动构造

在移动构造函数中,我们要做的就是转移成员数据。我们的MyClass有一个std::string类型的成员,该类型自身实现了移动语义,因此我们可以继续调用std::string类型的移动构造函数

 在有了移动构造函数之后,我们就可以在需要时通过它来创建新的对象,从而避免拷贝操作的开销 

int main()
{
  std::vector<wrt::MyClass> myClasses;
  wrt::MyClass tmp{ "hello" };
  myClasses.push_back({ std::move(tmp) }); //调用移动构造
  return 0;
}

还记得移动语义的精髓嘛?数据拿过来用就完事儿了。因此,在移动构造函数中,我们将传入对象A的数据转移给新创建的对象B。同时,还需要关注的重点在于,我们需要把传入对象A的数据清除,不然就会产生多个对象共享同一份数据的问题 

class MyClass
{
public:
    MyClass()
        : val{ 998 }
    {
        name = new char[] { "Peter" };
    }

    // 实现移动构造函数
    MyClass(MyClass&& rValue) noexcept
        : val{ std::move(rValue.val) }  // 转移数据
    {
        rValue.val = 0;  // 清除被转移对象的数据

        name = rValue.name;  // 转移数据
        rValue.name = nullptr;  // 清除被转移对象的数据
    }

    ~MyClass()
    {
        if (nullptr != name)
        {
            delete[] name;
            name = nullptr;
        }
    }

private:
    int val;
    char* name;
};
int main()
{
MyClass A{};
MyClass B{ std::move(A) };  // 通过移动构造函数创建新对象B
return 0;
}

4.移动赋值运算符

与拷贝构造函数和拷贝赋值运算符一样,除了移动构造函数之外,C++11还引入了移动赋值运算符。移动赋值运算符也是接收右值引用,它的实现和移动构造函数基本一致。在移动赋值运算符中,我们也是从传入的对象中转移数据,并将该对象的数据清除:

class MyClass
{
public:
    MyClass()
        : val{ 998 }
    {
        name = new char[] { "Peter" };
    }

    MyClass(MyClass&& rValue) noexcept
        : val{ std::move(rValue.val) }
    {
        rValue.val = 0;

        name = rValue.name;
        rValue.name = nullptr;
    }

    // 移动赋值运算符
    MyClass& operator=(MyClass&& myClass) noexcept
    {
        val = myClass.val;
        myClass.val = 0;  //数据清空

        name = myClass.name;
        myClass.name = nullptr; //数据置空

        return *this;
    }

    ~MyClass()
    {
        if (nullptr != name)
        {
            delete[] name;
            name = nullptr;
        }
    }

private:
    int val;
    char* name;
};
int main()
{
    MyClass A{};
    MyClass B{};
    B = std::move(A);  // 使用移动赋值运算符将对象A赋值给对象B
    return 0;
}

5. 移动构造函数和移动赋值运算符的生成规则

—— 定义一个空类,C++98中是默认生产四个函数:构造 析构 拷贝构造 拷贝赋值运算符函数

但是C++11中,我们写一个空类,会默认生成6个函数:原先的四个+移动构造+移动赋值运算符函数

 ——如果在类中定义了拷贝构造函数或者拷贝赋值运算符或者析构函数,那么编译器就不会自动生成移动构造函数和移动赋值运算符。此时,如果调用移动语义的话,由于编译器没有自动生成,因此会转而执行拷贝操作

——析构函数有一点值得注意,许多情况下,当一个类需要作为基类时,都需要声明一个virtual析构函数,此时需要特别留意是不是应该手动的为该类定义移动构造函数以及移动赋值运算符(没有定义的话会默认调用拷贝操作)此外,当子类派生时,如果子类没有实现自己的析构函数,那么将不会影响移动构造函数以及移动赋值运算符的自动生成: 

class MyClass
{
public:
    MyClass()
    {}

    // 我们定义了拷贝构造函数,这会禁止编译器自动生成移动构造函数和移动赋值运算符
    MyClass(const MyClass& value)
    {}
};

int main()
{
    MyClass A{};
    MyClass B{ std::move(A) };  // 执行的是拷贝构造函数来创建对象B
    return 0;
}

——如果我们在类中定义了移动构造函数,那么编译器就不会为我们自动生成移动赋值运算符。反之,如果我们在类中定义了移动赋值运算符,那么编译器也不会为我们自动生成移动构造函数

以移动构造函数为例,如果定义移动构造函数,编译器不会自动生成移动赋值运算符,此时,移动赋值运算符的调用并不会转而执行拷贝赋值运算符,而是会产生编译错误

class MyClass
{
public:
    MyClass()
    {}

    // 我们定义了移动构造函数,这会禁止编译器自动生成移动赋值运算符,并且对移动赋值运算符的调用会产生编译错误
    MyClass(MyClass&& rValue) noexcept
    {}
};
int main()
{
    MyClass A{};
    MyClass B{};
    B = std::move(A);  // 对移动赋值运算符的调用产生编译错误:attempting to reference a deleted function
    return 0;
}

6noexcept

其实刚才在实现移动构造函数的时候 还有看string中支持的移动构造函数都有一个关键字noexcept

首先介绍一个概念 "强异常保证(strong exception guarantee)"

所谓强异常保证,即当我们调用一个函数时,如果发生了异常,那么应用程序的状态能够回到函数调用之前

如果学习过C++异常处理的小伙伴一看这个关键字就非常熟悉,他的意思在这里就是不会抛出异常的移动构造函数

拷贝构造函数是会分配内存,因此很可能会抛出异常,但是移动构造是更改数据的所有权,所以一般不会抛异常

先来看一个没有noexcept的拷贝构造函数 

class MyClass
{
public:
    MyClass(int x)
        :_x(x)
    {}
    MyClass(const MyClass& lvalue) //拷贝构造函数
    {
        cout << "MyClass(const MyClass& lvalue" << endl;
        _x = lvalue._x;
        throw runtime_error("copy exception");
    }
    // 我们定义了移动构造函数,这会禁止编译器自动生成移动赋值运算符,并且对移动赋值运算符的调用会产生编译错误
    MyClass(MyClass&& rValue) noexcept
        :_x(std::move(rValue._x))
    {
        rValue._x = 0;
        cout << " MyClass(MyClass&& rValue) noexcept" << endl;
    }
    ~MyClass()
    {
        cout << "~MyClass()" << endl;
    }
 private:
     int _x;
};
int main()
{
    try
    {
        MyClass A{ 1 };
        MyClass B{A};

        //MyClass B{ std::move(A) };
    }
    catch(runtime_error e)
    {
        cout << "Catch!!!" <<e.what()<< endl;
    }
    return 0;
}

再来看移动拷贝里面抛异常的情况


class MyClass
{
public:
    MyClass(int x)
        :_x(x)
    {}
    MyClass(const MyClass& lvalue) //拷贝构造函数
    {
        cout << "MyClass(const MyClass& lvalue" << endl;
        _x = lvalue._x;
        throw runtime_error("copy exception");
    }
    // 我们定义了移动构造函数,这会禁止编译器自动生成移动赋值运算符,并且对移动赋值运算符的调用会产生编译错误
    MyClass(MyClass&& rValue) noexcept
        :_x(std::move(rValue._x))
    {
        rValue._x = 0;
        cout << " MyClass(MyClass&& rValue) noexcept" << endl;
        throw runtime_error("copy exception");

    }
    ~MyClass()
    {
        cout << "~MyClass()" << endl;
    }
 private:
     int _x;
};
int main()
{
    try
    {
        MyClass A{ 1 };
        MyClass B{ std::move(A) };
    }
    catch(runtime_error e)
    {
        cout << "Catch!!!" <<e.what()<< endl;
    }
    return 0;
}

但是如果你在声明了noexcept的移动构造函数中抛异常,此时会直接报错,并不会捕捉异常 

2.完美转发 

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发 

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}
int main()
{
	PerfectForward(10);  // 右值
	int a;
	PerfectForward(a);  // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b);  //const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

 

 std::forward 完美转发在传参的过程中保留对象原生类型属性

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t));   //完美转发
}
int main()
{
		PerfectForward(10);  // 右值
	int a;
	PerfectForward(a);  // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b);  //const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/720096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NSS [NSSRound#7 Team]ec_RCE

NSS [NSSRound#7 Team]ec_RCE 源码如下&#xff1a; <?PHPif(!isset($_POST["action"]) && !isset($_POST["data"]))show_source(__FILE__);putenv(LANGzh_TW.utf8); $action $_POST["action"];$data "".$_POST["…

代理IP,如何助力大数据时代

代理IP&#xff0c;如何为大数据助力 华科云商助力大数据 近年来&#xff0c;我国互联网商业保持持续发展的状态。大环境的优化&#xff0c;各项相关政策的出台&#xff0c;也为互联网经济的发展&#xff0c;提供了强有力的支持。大大小小的企业都想乘风起势&#xff0c;大展宏…

Django核心

安装django pip install django # pip install django3.1.6创建django项目 在一个项目中可以包含多个应用程序。 django-admin startapp app_name #创建一个应用程序 django-admin startproject project_name #创建一个项目运行django项目 python manage.py runserver 80…

REST风格讲解

1.REST风格简介 优点&#xff1a;隐藏资源访问的行为&#xff0c;无法通过地址得知对资源的操作&#xff0c;并且简化了书写 rest风格大概将请求方式分成了Get Post Put Delete四种操作方法。上述行为是约定的方式并不是规范。 RequestMapping注解里面value值存储访问的路…

Docker 中的 .NET 异常了怎么抓 Dump (转载)

一、背景 1. 讲故事 有很多朋友跟我说&#xff0c;在 Windows 上看过你文章知道了怎么抓 Crash, CPU爆高&#xff0c;内存暴涨 等各种Dump&#xff0c;为什么你没有写在 Docker 中如何抓的相关文章呢&#xff1f;瞧不上吗&#xff1f; 哈哈&#xff0c;在DUMP的分析旅程中&a…

【前端学JAVA】基础语法

作为一个前端程序员&#xff0c;其发展前途是远不及后端程序员的。因此&#xff0c;只有了解后端&#xff0c;才能让自己更加具备核心竞争力。本系列教程将以一个前端程序员的角度快速学习JAVA。 新建项目 开发JAVA程序&#xff0c;我们第一步是使用IDEA新建一个项目&#xf…

紫光展锐携手中国联通共建数字世界

6月28日&#xff0c;2023上海世界移动大会&#xff08;MWC上海&#xff09;首日&#xff0c;联通华盛总经理李立新、联通华盛副总经理陈丰伟一行莅临紫光展锐展台参观&#xff0c;紫光集团高级副总裁、紫光展锐CEO任奇伟博士&#xff0c;紫光展锐执行副总裁、工业电子事业部总经…

如何提高OAK相机在树莓派和JETSON上的运行帧率?

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手君。 最…

Lim接口测试平台-接口测试功能详解

一、接口测试 项目地址&#xff1a;Gitee/Github 接口测试模块是整个Lim平台的核心&#xff0c;左侧是接口的模块树&#xff0c;右侧顶部是用例操作功能区&#xff0c;列表展示接口用例信息&#xff1a; 文章目录 一、接口测试 二、维护接口用例 各步骤类型详解 1&#x…

picard的安装

最近在通过GATK所介绍的best practice流程来call SNP流程 1.流程 1.1 BWA比对&#xff0c;获得sam文件 1.2 准备用picard来压缩排序sam文件为bam文件&#xff0c;并对bam文件进行去重复&#xff08;duplicates marking&#xff09; 这是就需要用到picard软件 按照教程网页上…

Mac使用Puppeteer,并启动chromium

Mac使用Puppeteer&#xff0c;并启动chromium Puppeteer官网 chromium下载地址 通过chrome://version 可查询 Chromium 浏览器信息 const puppeteer require(puppeteer);(async () > {const browser await puppeteer.launch({executablePath: 上图可执行文件路径,headl…

爬取12306上所有城市的站台信息

0. 需求 爬取所有城市下的站台信息保存到Excel中: 效果: 1. 定位数据源 在12306随便一个车票查询页面上,通过F12控制台获取网站请求车站数据的URL 博主当前获取的URL为: https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version1.9270*注意…

服务器数据恢复-raid10重建为raid6的数据恢复案例

服务器故障&#xff1a; 一台IBM V7000存储中的vdisk丢失&#xff0c;Solaris操作系统中的部署的Oracle数据库不可用。经过和工作人员的沟通得知故障原因&#xff1a;工作人员进行重建MDisk的操作&#xff0c;将原先的raid10重建为raid6&#xff0c;然后又再次重建为raid10&…

RLHF文本生成图模型

背景 语言大模型有RLHF技术点&#xff0c;是否图生成也需要RLHF。要回答这个问题其实首先需要回答的问题有三个&#xff1a; 1.RLHF到底是个什么技术 2.为什么需要用RLHF技术&#xff0c;在语言大模型用RLHF模型解决什么问题点 3.图在什么情况下需要用到RLHF技术点 RLHF技…

如何实现监听某些数值,异步页面请求后再渲染到页面上

问题&#xff1a; 当我们遇到小程序在某个页面&#xff0c;需要刷新另一个页面的数据时&#xff0c;通常都是返回到刷新页面&#xff0c;然后执行onshow的函数。 但是是否可以拿数值之后&#xff0c;直接就更新相应的数值就行了&#xff1f;不用整体刷新。或者有时候页面已经…

7.3.4 【Linux】文件系统检验

xfs_repair 处理 XFS 文件系统 xfs_repair 可以检查/修复文件系统&#xff0c;不过&#xff0c;因为修复文件系统是个很庞大的任务&#xff01;因此&#xff0c;修复时该文件系统不能被挂载。 fsck.ext4 处理 EXT4 文件系统 fsck 是个综合指令&#xff0c;如果是针对 ext4 的…

“软件源 xxx 的选项 Signed-By 中含有互相冲突的值 xxx“解决方法

问题&#xff1a; 解决方法&#xff1a; 删除目录“/etc/apt/sources.list.d”下的所有文件。&#xff08;至于为什么&#xff0c;我也不知道&#xff09; 参考文章&#xff1a; Ubuntu18.04 安装Docker 报错&#xff1a;Signed-By 中含有互相冲突的值_Wynne然然不乖的博客-CS…

基于Springboot+Vue的房屋中介系统(源代码+数据库+14000字论文)064

基于SpringbootVue的房屋中介系统(源代码数据库14000字论文)064 一、系统介绍 本项目前后端分离 本系统分为管理员、用户、中介经纪人三种角色 用户角色包含以下功能&#xff1a; 登录、注册、个人信息修改、密码修改、房屋查看、经纪人联系 中介经纪人角色包含以下功能&…

开拓全球市场:国内企业如何通过TikTok直播开创品牌新篇章

在当今全球化的商业环境中&#xff0c;许多国内企业渴望将自己的品牌推向海外市场。然而&#xff0c;进军海外市场并不容易&#xff0c;尤其是对于那些缺乏国际推广经验的品牌来说。然而&#xff0c;随着社交媒体的兴起&#xff0c;一种新的推广方式正在引起关注&#xff0c;那…

软件测试方法

软件测试是在软件投入生产性运行之前&#xff0c;对软件需求分析、设计规格说明和编码的最终复审&#xff0c;是软件质量控制的关键步骤。 软件开发过程是一个自顶向下、逐步细化的过程&#xff0c;而测试过程则是依相反的顺序安排的自底向上、逐步集成的过程。 一、白盒测试…