【C++】023、移动语义&深拷贝

一、移动语言是如何解决深拷贝的性能问题?

  • 深拷贝会复制整个底层数据,让源对象和目标对象都拥有自己独立的堆内存空间

  • 移动语义是转换底层资源的所有权,将源对象的指针指向目标对象,而不赋值底层数据,时间复杂都为1

  • 移动语义将源对象的资源指针移动到目标对象,并将源对象指针设置为nullptr,这样源对象的析构操作就不会释放已经释放的资源了。

二、代码示例

  • 移动构造函数,与移动赋值操作符函数实现
cpp 复制代码
#include <iostream>
#include <cstring>

class MyString {
private:
    char* m_data;
    size_t m_len;

public:
    // ---------- 构造函数 ----------
    MyString(const char* str = "") 
        : m_len(strlen(str)), m_data(new char[m_len + 1]) {
        strcpy(m_data, str);
    }

    // ---------- 析构函数 ----------
    ~MyString() {
        delete[] m_data;  // 如果 m_data 是 nullptr,安全无操作
    }

    // ==========================================
    // 1. 深拷贝构造(O(n),昂贵)
    // ==========================================
    MyString(const MyString& other) 
        : m_len(other.m_len), m_data(new char[m_len + 1]) {
        strcpy(m_data, other.m_data);  // 申请内存 + 复制数据
        std::cout << "深拷贝构造(耗时 O(n))" << std::endl;
    }

    // ==========================================
    // 2. 移动构造(O(1),零开销)
    // ==========================================
    MyString(MyString&& other) noexcept 
        : m_data(other.m_data)    // ① 窃取指针(只复制地址)
        , m_len(other.m_len) {    // ② 窃取长度
        other.m_data = nullptr;   // ③ 关键!置空源对象,切断所有权
        other.m_len = 0;
        std::cout << "移动构造(O(1) 指针交换,未复制任何数据)" << std::endl;
    }

    // ==========================================
    // 3. 移动赋值(O(1))对比深拷贝赋值
    // ==========================================
    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] m_data;          // 释放自己的旧资源(如果有)
            m_data = other.m_data;    // 窃取指针
            m_len = other.m_len;
            other.m_data = nullptr;   // 置空源对象
            other.m_len = 0;
            std::cout << "移动赋值(O(1))" << std::endl;
        }
        return *this;
    }
};
  • 外部调用代码
cpp 复制代码
int main() {
    std::cout << "========== 场景1:拷贝构造 vs 移动构造 ==========" << std::endl;
    {
        MyString s1("HelloWorld");          // 1. 普通构造(分配堆内存)
        MyString s2(s1);                    // 2. 深拷贝构造:申请新内存 + 复制10个字符
        MyString s3(std::move(s1));         // 3. 移动构造:只交换指针,O(1)!

        // 注意:s1 现在是一个"空壳"(m_data = nullptr),不能再读取其内容!
        // 如果取消下面这行注释,程序会崩溃(访问空指针)。
        // std::cout << "s1 内容: " << s1.c_str() << std::endl; 

        std::cout << "s2 内容: " << s2.c_str() << " (独立内存)" << std::endl;
        std::cout << "s3 内容: " << s3.c_str() << " (窃取了 s1 的资源)" << std::endl;
    } // 作用域结束:s1(空)、s2、s3 各自析构,不会双重释放!
    std::cout << std::endl;

三、移动语义操作中置空源对象的原因?

1、防止双重释放:

  • 如果源对象的析构函数被调用时,m_data还指向原来的地址,他就会delete\[\]这块内存,而目标对象也指向这块地址,两个析构先后释放同一块内存,进程立即崩溃。

2、保证析构安全:

  • 置空后,源对象的析构函数执行delete\[\] m_data,由于m_data是nullp,delete\[\]是无害的空操作。

四、移动构造函数使用noexcept的原因?

  • C++标准库承诺vector扩容必须提供强异常安全保证,也就是扩容过程中如果抛出异常,程序的状态必须安全地恢复到扩容前的状态

  • 但是移动构造函数会将源对象置为空了,无法恢复

  • 所以加noexcept,相当于向编译器承诺此操作不会抛异常,让stl容器放心地使用移动,从而达到最高性能。

相关推荐
小星星闪亮登场12 分钟前
图论--最小生成树(内含二分图)
数据结构·算法·图论·迭代加深·图搜索算法
研☆香18 分钟前
数组方法 splice讲解 拓展
开发语言·前端·javascript
ZC跨境爬虫30 分钟前
LeetCode 219. 存在重复元素 II(滑动窗口 + 哈希表详解)
算法·leetcode·散列表
zh路西法33 分钟前
【玩转VLA具身智能机械臂】(三):视觉避障——从 RGB-D 点云到 MoveIt octomap 的完整落地
c++·ros2·gazebo·moveit2·ompl·octomap
峥嵘life36 分钟前
Android16 系统 APEX 模块说明
android·大数据·开发语言
吞下星星的少年·-·39 分钟前
The 2025 ICPC Asia East Continent Online Contest (I) A题(模拟+贪心)
算法·贪心·模拟
2601_9620715741 分钟前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
————A42 分钟前
Agent 文件处理中间件
开发语言·javascript·ecmascript
梦想的旅途242 分钟前
如何高效调用企业微信通讯录API管理组织架构
java·开发语言·企业微信
whcyhhh1 小时前
头歌实践教学平台:大数据存储2023(十三3)
大数据·开发语言·python