【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容器放心地使用移动,从而达到最高性能。

相关推荐
ctlover1 小时前
Python模块与包
开发语言·python
雨田言炎1 小时前
十、QThread多线程
linux·服务器·开发语言·前端·qt
月光船幽幽1 小时前
锁死后干预有效性的关键突破
人工智能·python·算法
2501_906565121 小时前
哥德巴赫猜想
算法
ShineWinsu1 小时前
对于C++中unordered_map的详细介绍
数据结构·c++·算法·面试·stl·哈希表·unordered_map
东东最爱敲键盘1 小时前
day3.c++函数与预处理 string
c++·算法
七夜zippoe2 小时前
基于 DolphinDB 构建全流程质量追溯体系:从原材料到成品的全链路追踪
大数据·人工智能·算法·全链路·dolphindb·质量追溯
过期的秋刀鱼!2 小时前
决策树-测量纯度
人工智能·python·深度学习·算法·决策树·机器学习·数据挖掘
rannn_1112 小时前
【力扣hot100】238、41、73题解
java·算法·leetcode·开发