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

news2025/1/11 8:57:11

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.coloradmin.cn/o/1082474.html

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

相关文章

kafka广播消费组停机后未删除优化

背景 kafka广播消息的时候为了保证groupId不重复&#xff0c;再创建的时间采用前缀时间戳的形式&#xff0c;这样可以保证每次启动的时候是创建的新的&#xff0c;但是 会出现一个问题&#xff1a;就是每次停机或者重启都会新建一个应用实例&#xff0c;关闭应用后并不会删除…

如何调整 Kubernetes StatefulSet 卷的大小

Kubernetes StatefulSet用于在集群内部署有状态应用程序。StatefulSet 中的每个 Pod 都可以访问即使在重新调度后仍坚持使用的本地持久卷。这使得 Pod 能够维护与其集合中的邻居不同的单独状态。 不幸的是,这些卷有一个很大的限制:Kubernetes 没有提供从 StatefulSet 对象调整…

排序算法-冒泡排序法(BubbleSort)

排序算法-冒泡排序法&#xff08;BubbleSort&#xff09; 1、说明 冒泡排序法又称为交换排序法&#xff0c;是从观察水中的气泡变化构思而成的&#xff0c;原理是从第一个元素开始&#xff0c;比较相邻元素的大小&#xff0c;若大小顺序有误&#xff0c;则对调后再进行下一个…

排序算法-选择排序法(SelectionSort)

排序算法-选择排序法&#xff08;SelectionSort&#xff09; 1、说明 选择排序法也是枚举法的应用&#xff0c;就是反复从未排序的数列中取出最小的元素&#xff0c;加入另一个数列中&#xff0c;最后的结果即为已排序的数列。选择排序法可使用两种方式排序&#xff0c;即在所…

基于nodejs+vue驾校预约管理系统

通过科技手段提高自身的优势&#xff1b;对于驾校预约管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了驾校预约管理系统&#xff0c; 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;驾校预约管理系统&am…

【Rust】包和模块,文档注释,Rust格式化输出

文章目录 包和模块包 CrateRust 的标准目录结构 模块 Module用路径引用模块使用super引用模块使用self引用模块结构体和枚举的可见性 使用 use 引入模块及受限可见性基本引入方式绝对路径引入模块相对路径引入模块中的函数 避免同名引用 注释和文档文档注释包和模块级别的注释注…

conda: error: argument COMMAND: invalid choice: ‘activate‘

参考:https://github.com/conda/conda/issues/13022 输入后重启terminal即可

Spring Boot 开发环境热部署

Spring Boot 项目无法像前端项目那样&#xff0c;修改源代码后刷新网页就能即时看到效果&#xff0c;需要先暂停运行&#xff0c;再重新启动&#xff0c;最后刷新网页。 为了避免这一麻烦的操作&#xff0c;我们可以设置热部署&#xff0c;启动服务后不论怎么修改源码&#xf…

NodeJs中使用JSONP和Cors实现跨域

跨域是为了解决浏览器请求域名&#xff0c;协议&#xff0c;端口不同的接口&#xff0c;相同的接口是不需要实现跨域的。 1.使用JSONP格式实现跨域 实现步骤 动态创建一个script标签 src指向接口的地址 定义一个函数和后端调用的函数名一样 实现代码 -- 在nodejs中使用http内…

【Mysql】重新认识mysql(一)

参考Mysql是怎么运行的&#xff0c;并结合实际的工作经验对mysql的知识进行总结。 Mysql架构 从大体上来说&#xff0c;Mysql是C/S架构。以我们平时使用的QQ为例&#xff0c;它其实是由两部分组成的&#xff0c;一部分是客户端程序,&#xff0c;一部分是服务器程序。客户端可…

【深蓝学院】手写VIO第6章--视觉前端--作业(SVD分解部分复习)

0. 题目 T1. 奇异值分解需要补&#xff0c;看这篇博客&#xff0c;讲的很好。 总结一下&#xff0c;关于奇异值分解(Singular Value Decomposition&#xff0c;SVD )有以下内容摘抄自该博客&#xff0c;关于SDV分解的部分应该是摘自李航《统计学习方法里面的》&#xff1a; 1…

互联网性能和可用性优化CDN和DNS

当涉及到互联网性能和可用性优化时&#xff0c;DNS&#xff08;Domain Name System&#xff09;和CDN&#xff08;Content Delivery Network&#xff09;是两个至关重要的元素。它们各自发挥着关键作用&#xff0c;以确保用户能够快速、可靠地访问网站和应用程序。在本文中&…

项目管理之常见七大问题挑战

在当今复杂多变的市场环境下&#xff0c;企业为了生存和发展&#xff0c;必须不断应对和解决各种挑战。其中&#xff0c;项目管理作为企业运营及项目交付等的重要组成部分&#xff0c;也面临着七大问题挑战。这些挑战不仅影响着项目的成功实施&#xff0c;也对企业的发展产生着…

[Spring] SpringMVC 简介(一)

目录 一、SpringMVC 简介 1、什么是 MVC 2、什么是 SpringMVC 3、SpringMVC 实现原理 4、SpringMVC 的特点 二、简单案例 1、引入依赖 2、在 web.xml 中配置前端控制器 DispatcherServlet 3、创建 SpringMVC 的配置文件 4、创建请求控制器 5、测试页面 6、访问不到 …

Go 存储系列:B+树存储引擎 boltdb

boltdb 介绍 boltdb是一个纯go编写的支持事务的文件型单机kv数据库 支持事务&#xff1a; boltdb数据库支持两类事务&#xff1a;读写事务、只读事务。这一点就和其他kv数据库有很大区别文件型&#xff1a; boltdb所有的数据都是存储在磁盘上的&#xff0c;所以它属于文件型数…

信号与系统第一章

文章目录 1.1连续信号与离散信号1.1.2信号能量与功率能量讨论无穷区间内功率和能量&#xff1a;无限区间内的平均功率&#xff1a;利用上述定义区分三种重要信号 1.2自变量的变换1.2.1举例基本变换1.2.2周期信号1.2.3偶信号与奇信号 1.3指数信号与正弦信号1.3.1连续时间复指数信…

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 …

python之计算市场技术指标

1、MA MA指标是一种常用的技术指标&#xff0c;它是通过计算一定时间内的股价平均值来反映股价趋势的指标。通常&#xff0c;MA指标越平滑&#xff0c;就能更好地反映出股价的长期趋势。 MA指标的作用是帮助投资者识别股票价格的趋势。当股票价格的MA指标向上运动时&#xff…

分类网络的评价指标

之前一直是做目标检测的研究&#xff0c;在目标检测中主要有两个任务&#xff0c;一个是分类回归&#xff0c;一个是位置回归&#xff0c;所用的评价指标有&#xff1a;AP&#xff0c;mAP&#xff0c;Recall&#xff0c;Precision&#xff0c;F1值&#xff0c;前两个用的一般最…

论文学习记录--零样本学习(zero-shot learning)

Socher R, Ganjoo M, Manning C D, et al. Zero-shot learning through cross-modal transfer[J]. Advances in neural information processing systems, 2013, 26. 注&#xff1a;中文为机翻 zero-shot learning&#xff1a;通过学习类别之间的关系和属性&#xff0c;使得模型…