c++ 类的特殊成员函数:移动构造函数(五)

1. 简介

移动构造函数是C++11中的新特性,它允许对象通过移动而不是复制来传递和初始化。移动构造函数通常用于提高性能,因为它避免了不必要的复制操作,特别是当处理大型对象或使用动态内存分配时。

2. 来源

当拷贝构造函数出现函数返回值 (返回对象)时,代码如下:

#include <iostream>
#include <string>using namespace std;class stu {
public:string* name = nullptr;int age;stu() {cout << "无参构造" << endl;}
//    stu() : name(nullptr) {
//        cout << "无参构造" << endl;
//    }stu(const string& n, int a) : name(new string(n)), age(a) {cout << "有参构造" << endl;}stu(const stu& s) : name(new string(*s.name)), age(s.age) {cout << "拷贝构造" << endl;}~stu() {delete name;  // 释放堆上分配的内存name = nullptr;cout << "析构函数" << endl;}
};stu createstu() {stu s("华云飞", 240);return s;
}int main() {stu s1 = createstu();return 0;
}

g++ 编译时运行输出如下:

有参构造
析构函数

MSVC 编译时运行输出如下:

有参构造
拷贝构造
析构函数
析构函数

这种现象是编译器自动优化,编译器有时候为了避免拷贝生成临时对象而消耗内存空间,所以默认会有优化、避免发生过多的拷贝动作所以打印的日志可能不是我们所期望的,这时候,如果手动编译的话,可以添加参数:

#如果手动编译 可以添加以下参数  -fno-elide-constructors 
g++ -std=c++11  xxx.cpp -fno-elide-constructors# 如果使用cmake编译,可以添加配置   
CMakeLists.txt 中前面添加:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-elide-constructors")

这时运行结果:

有参构造
拷贝构造
析构函数
拷贝构造
析构函数
析构函数

原因解释:
在这里插入图片描述

先创建 对象 s stu s("华云飞", 240), 然后执行拷贝构造得到临时对象,接着执行析构函数销毁对象s , 再执行拷贝构造得到对象 s1,接着执行析构函数销毁临时对象,最后再销毁对象 s1,完成整个代码过程。

2.1 tips:

如果编译信息中有如下提示:

cl: 命令行 warning D9002 :忽略未知选项“-fno-elide-constructors”

则:

在 CLion 中使用 cl 编译器时,如果看到类似的警告消息 "warning D9002: ignoring unknown option '-fno-elide-constructors'",这是因为 -fno-elide-constructors 是一个 g++(GCC)编译器选项,而不是 Visual C++(cl)编译器选项。-fno-elide-constructors 选项用于禁用 C++ 编译器对构造函数进行优化的过程。然而,Visual C++ 的 cl 编译器默认情况下不支持此选项。如果想在 CLion 中使用 -fno-elide-constructors 选项,需要将项目配置更改为使用 g++ 编译器而不是 cl 编译器。可以按照以下步骤进行更改:打开 CLion 并导航到 File -> Settings(Windows/Linux)或 CLion -> Preferences(MacOS)。
在设置面板中选择 Build, Execution, Deployment -> Toolchains。
在右侧的 "CMake" 栏中,点击下拉箭头选择已安装的 g++ 工具链。
确保勾选 "Use this toolchain for building" 复选框,并点击 Apply 或 OK 保存更改。
通过这样的配置更改,CLion 将使用 g++ 编译器来构建项目,应该能够正常使用 -fno-elide-constructors 选项了。请注意,在切换编译器之后可能需要重新加载项目或重新生成 CMake 配置。

如图:
在这里插入图片描述

3. 移动构造

拷贝构造有时会有一些弊端,数据拷贝太多,浪费内存,尤其是一些即将消亡的对象,这些对象销毁了,但是它们的数据还要拷贝出来,我们需要使用。而移动构造让新对象直接接管消亡对象的数据,不用重新开辟空间来拷贝数据。
代码:

#include <iostream>
#include <string>using namespace std;class stu {
public:string* name = nullptr;int age;stu(){cout << "无参构造" << endl;}
//    stu() : name(nullptr) {
//        cout << "无参构造" << endl;
//    }stu(const string& n, int a) : name(new string(n)), age(a) {cout << "有参构造" << endl;}stu(const stu& s) : name(new string(*s.name)), age(s.age) {cout << "拷贝构造" << endl;}// 移动构造函数  移动构造函数操作的是右值,也就是说,它主要是针对数据来说的。stu(stu && s){
//      让新对象持有数据的控制权name = s.name;age = s.age;
//      让原对象放弃数据的控制权  s.name = nullptr;s.age = 0;cout << "移动构造" << endl;}//    stu(stu && s):name(s.name),age(s.age){
//        s.name = nullptr;
//        cout << "移动构造" << endl;
//    }~stu() {if(name!= nullptr){delete name;  // 释放堆上分配的内存name = nullptr;}cout << "析构函数" << endl;}
};stu createstu() {stu s("华云飞", 240);cout << "s: " <<s.name << " " << *s.name<< " " << s.age << " " << &s.age <<  " " << &s<<endl;return s;
}int main() {stu s1 = createstu();cout << "s1: " <<s1.name << " " << *s1.name<< " " << s1.age  << " " << &s1.age <<  " " << &s1 <<endl;return 0;
}

运行结果:

有参构造
s: 000002452CA5FEA0 华云飞 240 000000F4F4F7FC60 000000F4F4F7FC58 
移动构造
析构函数
移动构造
析构函数
s1: 000002452CA5FEA0 华云飞 240  000000F4F4F7FD00 000000F4F4F7FCF8
析构函数

在这里插入图片描述

先执行有参构造 stu s("华云飞", 240) 生成对象s, 在执行移动构造把 对象s 中的数据的所有权递交给临时对象,同时让原对象放弃数据的控制权
在这里插入图片描述
同理 临时对象和 对象s1移动构造也是一样的。
在这里插入图片描述

4. std::move 函数

4.1 左值转右值

#include <iostream>
#include <string>using namespace std;int add(int &&a , int  && b){return a + b;
}int main() {int a = 20; // a : 左值  , 20 : 右值int & b = a ; // b : 左值引用 , a:左值int && c = 30 ; // c :右值引用 , 30 :右值//把左值变成右值。int && d = move(a); // d : 右值引用 , a :左值int num1 = 40 , num2 = 50;add(move(num1) ,move(num2));add(60 ,70);return 0;
}

4.2 对象转换为右值引用

std::move 是一个函数模板,用于将对象转换为右值引用,并且表明该对象的所有权可以被移动。当使用 std::move 将一个对象作为参数传递给其他函数时,意味着放弃了对该对象的所有权,并允许接收函数直接获取并修改其内部状态。这在实现高效的移动语义和避免不必要的拷贝构造/赋值操作时非常有用。

#include <iostream>
#include <string>using namespace std;class stu {
public:string* name = nullptr;int age;stu(){cout << "无参构造" << endl;}stu(const string& n, int a) : name(new string(n)), age(a) {cout << "有参构造" << endl;}stu(const stu& s) : name(new string(*s.name)), age(s.age) {cout << "拷贝构造" << endl;}stu(stu && s){name = s.name;age = s.age;s.name = nullptr;s.age = 0;cout << "移动构造" << endl;}~stu() {if(name!= nullptr){delete name;  // 释放堆上分配的内存name = nullptr;}cout << "析构函数" << endl;}
};int main() {stu s0;stu s("华云飞", 240);cout << "s: " <<s.name << " " << *s.name<< " " << s.age  << " " << &s.age <<endl;stu s2 = move(s);cout << "s2: " <<s2.name << " " << *s2.name<< " " << s2.age  << " " << &s2.age <<endl;return 0;
}

运行结果:

无参构造
有参构造
s: 00000223323E3F70 华云飞 240 0000007C39CFF880
移动构造
s2: 00000223323E3F70 华云飞 240 0000007C39CFF8D0
析构函数
析构函数
析构函数

5. 总结

C++中的移动构造函数是一种特殊的构造函数,用于在对象被移动时执行高效的资源转移操作。它主要用于提高程序的性能和内存管理效率。

在C++11及以上版本中引入了移动语义,通过右值引用(Rvalue Reference)来实现。移动构造函数使用 && 运算符作为参数类型标识,并且接受一个右值引用参数。通常情况下,它会将传入的参数对象的资源指针拷贝到当前对象,并将原始对象置为空状态,避免进行额外的资源拷贝或分配。

移动构造函数常用于以下场景:

  1. 在容器类中进行元素插入或重新排序时,可以利用移动语义避免不必要的数据复制。
    当使用动态容器(如 std::vector、std::list)存储大量对象时,容器可能会不断地进行内存重新分配和数据复制。在这种情况下,移动构造函数可以通过将对象的所有权从旧容器移动到新容器,避免不必要的数据复制,提高性能。
std::vector<BigObject> CreateBigObjects() {std::vector<BigObject> objects;// 添加大量对象到容器中// ...return objects;  // 移动构造函数被调用,避免了大量的数据复制
}
  1. 在函数返回值时,可以通过移动语义避免大量数据拷贝操作。
    临时对象的传递:当在函数间传递临时对象时,移动构造函数可以避免对临时对象进行深拷贝,提高效率。
void ProcessBigObject(BigObject obj) {// 处理大对象
}int main() {ProcessBigObject(BigObject());  // 临时对象通过移动构造函数传递,避免了深拷贝return 0;
}
  1. 在使用智能指针等管理资源的类中,可以通过移动语义进行资源所有权的转移。
    动态内存管理:当使用动态分配的内存(如使用 new 运算符分配的内存)来存储对象时,移动构造函数可以在对象的所有权转移后,正确管理内存的释放,避免内存泄漏。
std::unique_ptr<BigObject> CreateBigObject() {std::unique_ptr<BigObject> obj = std::make_unique<BigObject>();// 对对象进行初始化// ...return obj;  // 移动构造函数被调用,正确管理内存的释放
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode(力扣)416. 分割等和子集Python

LeetCode416. 分割等和子集 题目链接代码 题目链接 https://leetcode.cn/problems/partition-equal-subset-sum/ 代码 class Solution:def canPartition(self, nums: List[int]) -> bool:sum 0dp [0]*10001for num in nums:sum numif sum % 2 1:return Falsetarget …

【Vuex+ElementUI】Vuex中取值存值以及异步加载的使用

一、导言 1、引言 Vuex是一个用于Vue.js应用程序的状态管理模式和库。它建立在Vue.js的响应式系统之上&#xff0c;提供了一种集中管理应用程序状态的方式。使用Vuex&#xff0c;您可以将应用程序的状态存储在一个单一的位置&#xff08;即“存储”&#xff09;中&#xff0c;…

MySQL数据生成工具mysql_random_data_load

在看MySQL文章的时候偶然发现生成数据的工具&#xff0c;此处直接将软件作者的文档贴了过来&#xff0c;说明了使用方式及下载地址 Random data generator for MySQL Many times in my job I need to generate random data for a specific table in order to reproduce an is…

Flutter_Slider_SliderTheme_滑杆/滑块_渐变色

调用示例以及效果 SliderTheme(data: SliderTheme.of(context).copyWith(trackHeight: 3,// 滑杆trackShape: const GradientRectSliderTrackShape(radius: 1.5),// 滑块thumbShape: const GradientSliderComponentShape(rectWH: 14, overlayRectSpace: 4, overlayColor: Colou…

Linux sed命令

在Linux系统中&#xff0c;有许多强大的文本处理工具&#xff0c;其中之一就是sed&#xff08;Stream Editor&#xff09;命令。sed是一个用于对文本进行编辑、替换、删除和过滤操作的命令行工具&#xff0c;一次处理一行内容。它具有强大的正则表达式支持和灵活的文本处理功能…

智能电表线路单回路双回路的区别

随着科技的发展和能源管理的需求&#xff0c;智能电表已经成为电力系统中不可或缺的一部分。智能电表可以通过数据通信网络将用电信息实时传输到电力公司&#xff0c;为电力公司提供更精确、实时的用电数据&#xff0c;同时也可以为用户提供更加智能化的用电服务。 在智能电表…

Peter算法小课堂—DP背包问题

大家好&#xff0c;我是Peter&#xff0c;我又来啦&#x1f388;&#x1f384;✨ &#x1f388;&#x1f9e8;&#x1f389;《动态规划》专栏来啦&#xff0c;目前为止&#xff0c;此专栏已经有四篇文章啦&#x1f381;&#x1f380;&#x1f384; 1.DP概念与编程方法 DP概念…

图扑 HT for Web 风格属性手册教程

图扑软件明星产品 HT for Web 是一套纯国产化独立自主研发的 2D 和 3D 图形界面可视化引擎。HT for Web&#xff08;以下简称 HT&#xff09;图元的样式由其 Style 属性控制&#xff0c;并且不同类型图元的 Style 属性各不相同。为了方便查询和理解图元的 Style 属性&#xff0…

Matlab地理信息绘图—数据诊断

文章目录 数据诊断分析&#xff08;均值方差&#xff09;Matlab代码实现结果展示 数据诊断分析&#xff08;均值方差&#xff09; 均值方差检测是一种简单但有效的异常检测方法&#xff0c;主要基于样本的均值和方差的统计信息。该方法的核心思想是假设正常的样本点应该聚集在…

Python 自定义包和模块随机生成6位验证码(详解版)

一、新建一个包&#xff08;两种方法&#xff09; 方法一&#xff1a;先新建一个空目录命名为"小功能包"&#xff0c;然后在新建的目录下新建一个空__init__.py&#xff08;目的是声明当前目录是一个包&#xff09; 方法二&#xff1a;直接在PyCharm用鼠标依次点击F…

多尺度retinex图像去雾算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 clc; clear; close all; warning off; addpath(genpath(pwd)); rng(default)img_in im2doub…

基于SpringBoot的学院班级回忆录

目录 前言 一、技术栈 二、系统功能介绍 管理员模块的实现 用户信息管理 班委信息管理 班级信息管理 班级相册管理 用户和班委模块的实现 班委注册 班级信息管理 加入班级 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越…

使用c++视觉处理----canny 边缘检测、sobel边缘检测、scharr 滤波边缘检测

使用c视觉处理canny 边缘检测、sobel边缘检测、scharr 滤波边缘检测 #include <opencv2/opencv.hpp>int main() {// 读取图像cv::Mat image cv::imread("1.jpg", cv::IMREAD_GRAYSCALE); // 转为灰度图像if (image.empty()) {std::cerr << "无法加…

2023年9月Web3行业月度发展报告区块链篇 | 陀螺科技会员专享

9月是加密市场的活动月&#xff0c;斯坦福区块链周、Token2049等大型活动相继举办&#xff0c;后者更是创下超过1万人的历史最高纪录&#xff0c;成为了全球最大的Web3活动。在本次Token2049上&#xff0c;RWA、支付以及出入金成为了讨论度最多的活动。尽管活动如火如荼&#x…

流程自动化如何帮助简化安全性

正如帮助开发 IT 安全最佳实践的政府机构 NIST 所说&#xff0c;人们越来越认识到网络安全是“每个人的工作”。换句话说&#xff0c;不仅仅是 IT 组织内的技术员工必须帮助预防和检测网络安全风险。组织中的每个人&#xff0c;包括没有技术或网络安全背景的员工&#xff0c;都…

vue elementui的select组件实现滑到底部分页请求后端接口

vue elementui的select组件实现滑到底部分页请求后端接口 1.实现效果2.实现原理 1.实现效果 老规矩&#xff0c;直接上最后的实现效果 2.实现原理 直接上代码 <el-form-item class"diagmosisItem" label"诊断" v-scroll"handleScroll">…

【C进阶】内存函数

strcpy拷贝的仅仅是字符串&#xff0c;但是内存中的数据不仅仅是字符&#xff0c;所以就有了memcpy函数 1. memcpy void *memcpy &#xff08;void * destination &#xff0c;const void * source , size_t num) 函数memcpy从source的位置开始向后拷贝num个字节的数据到desti…

如何正确的关闭Redis服务器

Redis官方原生版本是在Linux平台上开发和测试的&#xff0c;但是大多数初学者都是使用Windows系统来学习如何开发的。因此&#xff0c;官方提供了一个叫做“Microsoft Open Tech Redis”的项目&#xff0c;该项目专门为Windows平台提供了一个官方支持的Redis版本&#xff0c;但…

大数据Doris(八):启动FE步骤

文章目录 启动FE步骤 一、配置环境变量 二、​​​​​​​创建doris-mate

C/C++ 线程超详细讲解(系统性学习day10)

目录 前言 一、线程基础 1.概念 2.一个进程中多个线程特征 2.1 线程共有资源 2.2 线程私有资源 3.线程相关的api函数 3.1 创建线程 创建线程实例代码如下&#xff1a; 需要特别注意的是&#xff1a; -lpthread和-pthread的区别 3.2 给线程函数传参 传参实例代码如…