资讯动态

C++ 深浅拷贝

发布时间:2026/10/8 23:39:48 来源:尧图企业网站定制
在 C 里深浅拷贝不是语法关键字而是资源管理策略。默认拷贝对裸指针是浅拷贝深拷贝要自己写现代 C 优先 Rule of Zero。1. C 中拷贝发生在哪C 里有两个容易混淆的动作Buffer a(10); Buffer b a; // 拷贝构造用 a 初始化 b Buffer c(5); c a; // 拷贝赋值把 a 赋给已存在的 c拷贝构造T(const T other)创建新对象。拷贝赋值T operator(const T other)已有对象被赋值。如果自己不写编译器会隐式生成默认版本。默认版本做的事情是成员逐一拷贝。对int、double等标量直接复制值。对std::string、std::vector等类类型成员调用它们自己的拷贝构造。对裸指针成员只复制指针地址。所以关键不是“默认拷贝一定是浅拷贝”而是默认拷贝对裸指针/句柄是浅拷贝对 RAII 类型成员则依赖该类型自己的拷贝语义。2. 浅拷贝只复制指针不复制资源看一个危险例子#include cstring class Shallow { public: explicit Shallow(const char* s) : data_(new char[std::strlen(s) 1]) { std::strcpy(data_, s); } ~Shallow() { delete[] data_; } // 没有自定义拷贝构造和拷贝赋值 // 编译器生成默认版本浅拷贝 private: char* data_; };使用cppint main() { Shallow a(hello); Shallow b a; // 默认拷贝构造b.data_ a.data_ }问题a.data_和b.data_指向同一块堆内存。作用域结束时a和b都会delete[] data_。同一块内存被释放两次double free程序崩溃或未定义行为。浅拷贝还可能带来悬垂指针意外修改改b影响a资源泄漏或重复释放。3. 深拷贝分配新资源复制内容深拷贝要求新对象拥有自己的资源与原对象完全独立。#include algorithm #include cstddef #include cstring #include utility class Buffer { public: explicit Buffer(std::size_t n 0) : size_(n), data_(n ? new char[n]{} : nullptr) {} explicit Buffer(const char* s) : size_(std::strlen(s)), data_(new char[size_ 1]) { std::memcpy(data_, s, size_ 1); } ~Buffer() { delete[] data_; } // 深拷贝构造 Buffer(const Buffer other) : size_(other.size_), data_(other.size_ ? new char[other.size_] : nullptr) { if (size_ 0) { std::copy(other.data_, other.data_ other.size_, data_); } } // 深拷贝赋值copy-and-swap Buffer operator(const Buffer other) { if (this ! other) { Buffer tmp(other); swap(tmp); } return *this; } // 移动构造转移资源不复制 Buffer(Buffer other) noexcept : size_(other.size_), data_(other.data_) { other.size_ 0; other.data_ nullptr; } // 移动赋值 Buffer operator(Buffer other) noexcept { if (this ! other) { delete[] data_; size_ other.size_; data_ other.data_; other.size_ 0; other.data_ nullptr; } return *this; } void swap(Buffer other) noexcept { std::swap(size_, other.size_); std::swap(data_, other.data_); } std::size_t size() const { return size_; } const char* data() const { return data_; } private: std::size_t size_; char* data_; };现在Buffer a(hello); Buffer b a; // 深拷贝b 有自己的内存 Buffer c(3); c a; // 深拷贝赋值c 也独立修改b或c不会影响a。4. 拷贝赋值为什么要 copy-and-swap拷贝赋值最容易踩的坑自赋值a a;异常安全分配新内存失败时不能破坏原对象。copy-and-swap 写法Buffer operator(const Buffer other) { if (this ! other) { Buffer tmp(other); // 先拷贝失败也不影响 *this swap(tmp); // 交换后tmp 持有旧资源 } return *this; } // tmp 析构释放旧资源优点自赋值安全强异常安全拷贝逻辑复用拷贝构造。5. 移动语义不是拷贝是资源转移深拷贝很贵。如果源对象马上要销毁就没必要复制资源直接“偷”过来即可。Buffer a(100); Buffer b std::move(a); // 移动构造移动后b拿到a原来的指针和大小a被置空没有堆内存复制通常 O(1)。移动构造和移动赋值应尽量标记noexcept这样std::vector扩容时才会优先移动而不是拷贝。注意std::move本身不移动只是把表达式转换成右值引用。6. Rule of Three / Five / ZeroRule of Three如果你需要自定义以下三者中的任意一个析构函数拷贝构造拷贝赋值通常三个都需要。因为这说明类在管理资源。只写析构不写拷贝就会掉进浅拷贝陷阱。Rule of FiveC11 之后再加上移动构造移动赋值。合称五法则。上面的Buffer就是五法则实现。Rule of Zero最推荐不要自己管理资源。用std::string、std::vector、std::unique_ptr、std::shared_ptr等 RAII 类型作为成员让编译器生成正确的拷贝、移动和析构。class Person { public: Person(std::string name, std::vectorint scores) : name_(std::move(name)), scores_(std::move(scores)) {} private: std::string name_; std::vectorint scores_; };这个类不需要写析构、拷贝构造、拷贝赋值、移动构造、移动赋值。编译器生成的版本会正确调用std::string和std::vector的拷贝/移动。7. 常见 RAII 类型的拷贝语义类型拷贝行为说明std::string深拷贝新字符串拥有自己的缓冲区std::vectorT深拷贝新 vector 拥有自己的元素存储std::unique_ptrT禁止拷贝只能移动独占所有权std::shared_ptrT共享所有权拷贝只增加引用计数不复制对象裸指针T*浅拷贝只复制地址危险自定义资源类看实现需要自己决定深/浅/移动示例std::vectorint a {1, 2, 3}; std::vectorint b a; // 深拷贝 b[0] 100; // a[0] 仍然是 1auto p1 std::make_sharedBuffer(10); auto p2 p1; // 共享同一个 Buffer不是深拷贝如果你想要shared_ptr的深拷贝需要手动克隆auto p3 std::make_sharedBuffer(*p1); // 调用 Buffer 的拷贝构造8. 面试常见问题Q默认拷贝构造是浅拷贝吗A默认拷贝构造是成员逐一拷贝。对裸指针成员它复制地址是浅拷贝对std::string、std::vector等成员会调用它们的拷贝构造通常是深拷贝。Q浅拷贝会导致什么问题A共享资源可能出现 double free、悬垂指针、意外修改。Q深拷贝怎么实现A在拷贝构造中分配新资源并复制内容在拷贝赋值中用 copy-and-swap 或先释放再复制并处理自赋值。Q拷贝赋值为什么需要自赋值检查A防止先释放自己的资源再从已释放的资源拷贝。Q移动和拷贝的区别A拷贝复制资源移动转移资源源对象变成有效但未指定状态。Q什么时候用 Rule of ZeroA优先使用 RAII 成员避免手动管理资源让编译器生成特殊成员函数。9. 总结C 默认拷贝是成员逐一拷贝。裸指针成员默认拷贝是浅拷贝共享资源。深拷贝需要自己分配资源并复制内容。管理资源的类通常遵循Rule of Three / Five。现代 C 优先Rule of Zero用string、vector、智能指针。移动语义用于转移资源不是拷贝。unique_ptr不可拷贝shared_ptr拷贝是共享所有权不是深拷贝。一句话裸指针默认浅拷贝资源类要深拷贝现代 C 尽量零法则。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑