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

相关推荐
zhangzeyuaaa1 小时前
Ruby 多线程、GVL(GIL)与 Mutex 完全指南
开发语言·前端·ruby
weiabc1 小时前
MSYS2 UCRT64 + g++ C++ 中文输出乱码 完整全过程
c++
谢亮_vipxieliang2 小时前
用 PHP 构建轻量级 REST API:从路由到鉴权的完整实践
开发语言·后端·php
思无邪662 小时前
用 AI 做 JS 逆向:从抓包到复现的完整方法论
开发语言·javascript·人工智能
灯澜忆梦2 小时前
【面向对象编程C++】| 基础语法
java·c++·算法
小蒋观天下2 小时前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型
Evand J2 小时前
【电机滤波例程5】渐消因子自适应扩展卡尔曼滤波(EKF),MATLAB。PMSM负载突变下的状态估计。附下载链接
算法·matlab
H.莓飛3 小时前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法
好想像大佬一样能够ak所有3 小时前
Leetcode35.搜索插入位置
数据结构·算法·leetcode
aqiu1111113 小时前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode