C++--深拷贝三件套 + swap + 写时拷贝 + vector 扩容 + reserve


博主名称:deM0one ⭐️C++专栏: C++ ⭐️C++string第一讲: string第一讲 ⭐️C++string第二讲: string第二讲 ⭐️C++string第三讲: string第三讲

文章目录

  • [C++ string 深拷贝、swap 与容量管理(今日学习内容)](#C++ string 深拷贝、swap 与容量管理(今日学习内容))
    • 一、引言
      • [1.1 本文内容概览](#1.1 本文内容概览)
      • [1.2 前置知识](#1.2 前置知识)
    • [二、string 的深拷贝:构造函数](#二、string 的深拷贝:构造函数)
      • [2.1 思路](#2.1 思路)
      • [2.2 代码实现](#2.2 代码实现)
    • [三、拷贝构造:复用构造函数 + swap](#三、拷贝构造:复用构造函数 + swap)
      • [3.1 思路](#3.1 思路)
      • [3.2 代码实现](#3.2 代码实现)
    • [四、赋值运算符 operator=](#四、赋值运算符 operator=)
      • [4.1 思路](#4.1 思路)
      • [4.2 代码实现](#4.2 代码实现)
      • [4.3 理解要点](#4.3 理解要点)
    • [五、swap 的实现与优化](#五、swap 的实现与优化)
      • [5.1 成员函数 swap](#5.1 成员函数 swap)
      • [5.2 通用 std::swap 的问题](#5.2 通用 std::swap 的问题)
      • [5.3 string 特化的 std::swap](#5.3 string 特化的 std::swap)
    • 六、引用计数与写时拷贝(COW)
      • [6.1 为什么会有 COW](#6.1 为什么会有 COW)
      • [6.2 浅拷贝的两个经典问题](#6.2 浅拷贝的两个经典问题)
      • [6.3 写时拷贝(Copy On Write)思想](#6.3 写时拷贝(Copy On Write)思想)
      • [6.4 实验:同一份代码,不同平台表现不同](#6.4 实验:同一份代码,不同平台表现不同)
      • [6.5 底层结构(g++ COW 实现)](#6.5 底层结构(g++ COW 实现))
    • [七、vector 扩容实验](#七、vector 扩容实验)
      • [7.1 实验代码](#7.1 实验代码)
      • [7.2 实验结果对比](#7.2 实验结果对比)
      • [7.3 结论](#7.3 结论)
    • 八、reserve:请求调整容量
      • [8.1 std::string::reserve](#8.1 std::string::reserve)
      • [8.2 std::vector::reserve](#8.2 std::vector::reserve)
      • [8.3 示例:reserve 只影响 capacity](#8.3 示例:reserve 只影响 capacity)
      • [8.4 注意事项](#8.4 注意事项)
    • [九、size 与 capacity 的关系](#九、size 与 capacity 的关系)
      • [9.1 示意图](#9.1 示意图)
      • [9.2 理解要点](#9.2 理解要点)
    • 十、总结
    • 任何的批评与建议欢迎指出,我们共同进步!

C++ string 深拷贝、swap 与容量管理(今日学习内容)

本文档基于学习笔记整理,内容覆盖:string 的构造/拷贝构造/赋值、swap 的优化、引用计数与写时拷贝(COW)、vector 扩容规律、reserve 的作用,以及 size 与 capacity 的关系。

一、引言

1.1 本文内容概览

  • 自己实现 string 的"拷贝三件套":构造函数、拷贝构造、赋值运算符
  • 用 swap 实现高效且异常安全的拷贝语义
  • 深拷贝 vs 浅拷贝:为什么会有引用计数与写时拷贝(COW)
  • 从实验数据看 vector 的扩容策略(VS 与 g++ 的差异)
  • reserve 的语义与使用注意事项

1.2 前置知识

  • string 的基本使用、new / delete 动态内存管理
  • size(有效字符数)与 capacity(容量)的区别

二、string 的深拷贝:构造函数

2.1 思路

  • 用 strlen 求长度,按需开空间,再用 strcpy 拷贝内容
  • 注意:capacity 要额外包含结尾的 '\0'

2.2 代码实现

cpp 复制代码
string(const char* str)
    : _str(nullptr), _size(0), _capacity(0)
{
    _size = strlen(str);
    _capacity = _size;                // _capacity 包含 '\0'
    _str = new char[_capacity + 1];   // 多开一个位置放 '\0'
    strcpy(_str, str);
}

三、拷贝构造:复用构造函数 + swap

3.1 思路

  • 不自己写拷贝逻辑,而是复用已有的构造函数生成临时对象,再交换过来
  • 代码简洁、异常安全

3.2 代码实现

cpp 复制代码
string(const string& s)
    : _str(nullptr), _size(0), _capacity(0)
{
    string tmp(s._str);   // 复用构造函数,完成深拷贝
    swap(tmp);            // 与临时对象交换,tmp 销毁时带走旧资源
}

四、赋值运算符 operator=

4.1 思路

  • 先判断自赋值this != &s
  • 同样用"拷贝 + swap"的方式,简单且安全

4.2 代码实现

cpp 复制代码
string& operator=(const string& s)
{
    if (this != &s)        // 防止自赋值
    {
        string tmp(s);     // 深拷贝
        swap(tmp);         // 交换,tmp 析构时释放旧空间
    }
    return *this;
}

4.3 理解要点

  • 拷贝构造、赋值都走"临时对象 + swap",统一了逻辑
  • swap 只交换内部成员,代价极低(O(1))

五、swap 的实现与优化

5.1 成员函数 swap

cpp 复制代码
void swap(string& s)
{
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

5.2 通用 std::swap 的问题

  • 通用模板的行为等价于:
cpp 复制代码
template <class T>
void swap(T& a, T& b)
{
    T c(a);   // 1 次深拷贝
    a = b;    // 1 次深拷贝
    b = c;    // 1 次深拷贝
}
  • 3 次深拷贝,对 string 这种带堆内存的类代价很大

5.3 string 特化的 std::swap

  • 标准库为 string 提供了 std::swap(string& x, string& y)重载
  • 作用:交换两个 string 对象的值,效果等价于调用 x.swap(y)
  • 优化原理:只交换内部数据(指针),不交换字符内容,性能更好

六、引用计数与写时拷贝(COW)

6.1 为什么会有 COW

  • 深拷贝:每次拷贝都复制全部字符,代价高
  • 浅拷贝:多个对象共享同一块资源,只需要一个引用计数
  • 引用计数 = 有几个对象指向这块资源

6.2 浅拷贝的两个经典问题

  1. 析构两次:两个对象析构时都去 delete 同一块内存
  2. 一个修改影响另一个:共享内存导致数据互相干扰

6.3 写时拷贝(Copy On Write)思想

  • 博弈:如果拷贝后不写,是不是就赚了?
  • 策略:拷贝时先共享资源、只增加引用计数;真正要修改时才深拷贝一份

6.4 实验:同一份代码,不同平台表现不同

cpp 复制代码
std::string s1("111111111111111111111111");
std::string s2(s1);            // 拷贝构造

printf("%p\n", s1.c_str());
printf("%p\n", s2.c_str());
  • VS2019:两次打印地址不同(如 009315D8 / 009316C8)→ 默认深拷贝
  • g++ 4.8.5(Linux):两次打印地址相同(如 0x17a6028 / 0x17a6028)→ 使用引用计数 + 写时拷贝

6.5 底层结构(g++ COW 实现)

  • 内部用 _Rep_base 结构管理共享资源:
    • _M_length:长度
    • _M_capacity:容量
    • _M_refcount:引用计数(原子变量,保证线程安全)

七、vector 扩容实验

7.1 实验代码

cpp 复制代码
void TestVectorExpand()
{
    vector<int> v;
    size_t sz = v.capacity();
    cout << "making v grow:" << endl;

    for (int i = 0; i < 199; ++i)
    {
        v.push_back(i);
        if (sz != v.capacity())
        {
            sz = v.capacity();
            cout << "capacity changed: " << sz << endl;
        }
    }
}

7.2 实验结果对比

平台 扩容倍数 capacity 变化序列
VS2019 约 1.5 倍 0, 1, 2, 3, 4, 6, 9, 13, 19, 28, 42, 63, 94, 141
g++ 4.8 2 倍 0, 1, 2, 4, 8, 16, 32, 64, 128

7.3 结论

  • VS2019 约 1.5 倍扩容,g++ 约 2 倍扩容------扩容策略由标准库实现决定,不同平台可能不同
  • 扩容会重新分配内存并拷贝已有元素,代价较高

八、reserve:请求调整容量

8.1 std::string::reserve

cpp 复制代码
void reserve(size_t n = 0);
  • 作用:请求把容量调整为"最多容纳 n 个字符"
  • 如果 n 大于当前容量:扩容到 n(或更大)
  • 其他情况:只是一个非强制请求,实现可以优化或保持原容量
  • 不影响字符串长度,也不能修改内容

8.2 std::vector::reserve

cpp 复制代码
void reserve(size_type n);
  • 作用:请求容量至少能容纳 n 个元素
  • n 大于当前容量:重新分配存储,扩容到 n(或更大)
  • n 不大于当前容量:不会重新分配,容量不受影响
  • 不影响 size,也不能修改元素

8.3 示例:reserve 只影响 capacity

cpp 复制代码
void test_vector2()
{
    vector<int> v(10);        // size = 10, capacity = 10
    v.reserve(20);            // 扩容到 20
    cout << v.size() << endl;      // 10
    cout << v.capacity() << endl;  // 20

    v.reserve(0);             // 请求缩容
    cout << v.size() << endl;      // 10(size 不变)
    cout << v.capacity() << endl;  // 20(不缩容)
}

8.4 注意事项

  • reserve 只影响 capacity,不会改变 size
  • 用 reserve 提前预留空间,可以避免频繁扩容

九、size 与 capacity 的关系

9.1 示意图

复制代码
|----- 有效数据 (size = 5) -----|--------- 预留空间 (capacity = 20) ---------|
|        删除数据                |        插入数据,空间不够就扩容             |

9.2 理解要点

  • size 之前的区域存放有效数据,删除数据在此范围内进行
  • size 到 capacity 之间是预留空间,插入数据时先使用预留空间,不够了才扩容
  • 扩容的本质:重新开一块更大的空间,把已有数据搬过去

十、总结

  • 深拷贝三件套(构造/拷贝构造/赋值)用"临时对象 + swap"实现,简洁且异常安全
  • swap 只交换内部指针,是 O(1) 操作;string 特化了 std::swap,避免通用版本的 3 次深拷贝
  • 浅拷贝有"析构两次"和"互相影响"两个问题,写时拷贝(COW)通过引用计数在"读共享、写才拷"之间做平衡
  • 同一份代码在不同平台(VS2019 / g++ 4.8)可能表现出深拷贝与 COW 的差异
  • vector 扩容策略不统一:VS2019 约 1.5 倍,g++ 约 2 倍
  • reserve 只调整 capacity、不影响 size 和内容,适合提前预留空间

任何的批评与建议欢迎指出,我们共同进步!

相关推荐
thefool1122661 小时前
翻转二叉树
java
坐吃山猪1 小时前
WebClient内存配置解析
开发语言·springboot·webflux
jdksjw2 小时前
同步与异步、阻塞与非阻塞、多线程、协程超详细讲解(Python并发编程从入门到精通)
开发语言·python
Zach_菠萝侠2 小时前
【DeepSeek Harness 研究】进化方向4:安全加固 思考、设计与实现
开发语言·安全·deepseek
喜欢打篮球的普通人2 小时前
LLVM Backend Lowering 从入门到实战:把 IR 变成机器码的完整链路
android·java·数据库
ShineWinsu2 小时前
对于C++:布隆过滤器(bloomfilter)的详细解析
c++·面试·位图·位运算·布隆过滤器·海量数据·比特位
FL16238631292 小时前
室内易燃物识别易燃评估室内易燃程度识别分割数据集labelme格式1015张85类别
java·服务器·前端
SL-staff2 小时前
技术实践:HR如何用JVS-Logic可视化编排实现考勤数据同步(含节点配置与异常处理)
开发语言·python·低代码·钉钉·可视化编排·jvs-logic·hr技术
jufeng13073 小时前
【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】
java·spring boot·时序数据库·tdengine