C++学习笔记——this关键字、对象生命周期(栈作用域)、智能指针、复制与拷贝构造函数

张开发
2026/4/18 0:43:23 15 分钟阅读

分享文章

C++学习笔记——this关键字、对象生命周期(栈作用域)、智能指针、复制与拷贝构造函数
目录1. this关键字1.1 this 的本质1.2 const 成员函数中的 this2. 对象生命周期栈作用域2.1 基本概念2.2 作用域指针3. 智能指针3.1 为什么需要智能指针3.2 std::unique_ptr独占所有权3.3 std::shared_ptr共享所有权3.4 std::weak_ptr弱引用3.5 注意事项3.6 使用总结4. 复制与拷贝构造函数4.1 基本概念4.2 浅拷贝 vs 深拷贝4.3 避免隐式拷贝何时需要按值传递1. this关键字1.1 this 的本质this是一个指向当前对象的指针ClassName* const this在非静态成员函数内部可用。它由编译器隐式传递给成员函数作为第一个隐藏参数指向调用该成员函数的对象实例。class Entity { private: int m_X; public: void SetX(int x) { // 编译器自动将 this 传入this 指向调用该函数的对象 this-m_X x; // 等价于 m_X x; } };1.2 const 成员函数中的 this在const 成员函数中this的类型是const ClassName* const指向常量的常量指针。这意味着不能通过this修改任何非 mutable 成员变量。class Entity { int m_X; public: int GetX() const { // this 的类型是 const Entity* // this-m_X 10; 错误不能修改 return this-m_X; // 允许读取 } };2. 对象生命周期栈作用域2.1 基本概念对象生命周期从对象创建构造函数执行到销毁析构函数执行的时间段。栈作用域对象在进入作用域时创建离开作用域时自动销毁自动存储期。这是 C 最安全、最高效的内存管理方式。void function() { Entity e; // 进入作用域构造 // ... 使用 e } // 离开作用域自动析构2.2 作用域指针class ScopedPtr { private: Object* m_Ptr; public: ScopedPtr(Object* ptr) : m_Ptr(ptr) {} ~ScopedPtr() { delete m_Ptr; } }; int main() { { ScopedPtr e new Object(5); // 隐式转换new Object(5) 传给构造函数 } // 离开作用域~ScopedPtr 自动 delete m_Ptr std::cin.get(); }利用栈对象的自动析构确保资源内存、文件句柄、锁等不会泄漏。3. 智能指针3.1 为什么需要智能指针原始指针Object*需要手动delete容易忘记导致内存泄漏或过早delete导致悬空指针。智能指针利用栈对象的析构自动释放堆内存遵循 RAII 原则。C11 标准库提供了三种智能指针std::unique_ptr、std::shared_ptr、std::weak_ptr。3.2std::unique_ptr独占所有权独占同一时刻只能有一个unique_ptr指向一块内存。不可拷贝只能移动拷贝构造和拷贝赋值运算符重载被删除但支持移动语义std::move。开销小与原始指针大小相同析构时自动delete内部指针。#include memory // 推荐使用 std::make_unique (C14) auto ptr std::make_uniqueObject(5); // C11 需使用原始 new std::unique_ptrObject ptr(new Object(5)); std::unique_ptrObject a std::make_uniqueObject(); // std::unique_ptrObject b a; // 错误不能拷贝 std::unique_ptrObject b std::move(a); // 正确a 转移所有权给 ba 变为空3.3 std::shared_ptr共享所有权多个指针共享同一对象内部使用引用计数reference count记录有多少个shared_ptr指向同一块内存。当最后一个shared_ptr被销毁时引用计数降为 0自动释放对象内存。引用计数存储在控制块control block中与对象内存分离。// 推荐std::make_shared一次分配同时创建对象和控制块效率更高 auto ptr std::make_sharedObject(5); // 也可以使用原始 new std::shared_ptrObject ptr(new Object(5)); std::shared_ptrObject sp1 std::make_sharedObject(); // 引用计数 1 { std::shared_ptrObject sp2 sp1; // 引用计数 2 // sp2 离开作用域引用计数变为 1 } // 此时引用计数 1对象仍未释放 sp1.reset(); // 引用计数 0对象释放3.4 std::weak_ptr弱引用不增加引用计数弱引用指向shared_ptr管理的对象但不拥有所有权。可能悬空对象可能已被其他shared_ptr释放。必须转换为shared_ptr才能访问对象通过lock()方法。std::shared_ptrObject sp std::make_sharedObject(); std::weak_ptrObject wp sp; // wp 观察 sp引用计数不变 if (auto locked wp.lock()) { // 尝试提升为 shared_ptr locked-DoSomething(); // 安全访问 } else { // 对象已被释放 }3.5 注意事项循环引用两个shared_ptr互相引用对方例如双向链表导致引用计数永远不为 0内存泄漏。解决办法是使用weak_ptr打破循环。#include iostream #include memory class Node { public: std::shared_ptrNode next; // 指向下一个节点 std::shared_ptrNode prev; // 指向前一个节点 int value; Node(int val) : value(val) { std::cout Node( value ) constructed\n; } ~Node() { std::cout Node( value ) destructed\n; } }; int main() { { auto node1 std::make_sharedNode(1); auto node2 std::make_sharedNode(2); node1-next node2; // node1 的 next 指向 node2 node2-prev node1; // node2 的 prev 指向 node1 // 此时引用计数 // node1: 自身 (1) node2-prev (1) 2 // node2: 自身 (1) node1-next (1) 2 } // 离开作用域node1 和 node2 的 shared_ptr 局部变量销毁 // 但 node1 和 node2 的引用计数各减 1 后变为 1因为互相仍有引用 // 内存永远不会释放析构函数不会调用 std::cout End of main (memory leaked)\n; return 0; }解决方案使用weak_ptr打破循环#include iostream #include memory class Node { public: std::shared_ptrNode next; // 下一个节点强引用 std::weak_ptrNode prev; // 前一个节点弱引用不增加引用计数 int value; Node(int val) : value(val) { std::cout Node( value ) constructed\n; } ~Node() { std::cout Node( value ) destructed\n; } }; int main() { { auto node1 std::make_sharedNode(1); auto node2 std::make_sharedNode(2); node1-next node2; // node2 引用计数 1 → 2 node2-prev node1; // 弱引用node1 引用计数不变仍为 1 // 此时引用计数 // node1: 自身 (1) 因为 prev 是 weak_ptr不增加 // node2: 自身 (1) node1-next (1) 2 } // 离开作用域 // node1 销毁 → 引用计数 1→0释放 node1调用析构 // node2 销毁 → 引用计数 2→1因为 node1-next 已随 node1 销毁而销毁 // 然后 node2 引用计数变为 0释放 node2 std::cout End of main (no leak)\n; return 0; }性能开销比unique_ptr多一份控制块内存和原子操作线程安全的引用计数适合真正需要共享所有权的场景。3.6 使用总结{ std::shared_ptrObject obj; { std::shared_ptrObject sharedObj std::make_sharedObject(); // make_shared 一次分配对象内存 控制块引用计数初始为 1 obj sharedObj; // 拷贝赋值引用计数变为 2 std::weak_ptrObject weakObj obj; // 弱引用不增加计数 std::unique_ptrObject object std::make_uniqueObject(); // std::unique_ptrObject obj object; // 错误unique_ptr 不可拷贝 object-Print(); } // 离开内层作用域 // - sharedObj 销毁引用计数从 2 减为 1因为 obj 仍持有 // - objectunique_ptr销毁自动 delete 其管理的 Object // - weakObj 销毁不影响引用计数 } // 离开外层作用域 // - obj 销毁引用计数从 1 减为 0delete 管理的内存make_shared优于直接new一次分配对象控制块异常安全效率高。shared_ptr支持拷贝引用计数随之增减。weak_ptr不增加计数用于观察和打破循环。unique_ptr不可拷贝只能移动。4. 复制与拷贝构造函数4.1 基本概念复制用一个已存在的对象创建另一个新对象新对象与原对象内容相同但通常应是独立的内存副本。拷贝构造函数一种特殊的构造函数参数是当前类的 const 引用用于定义“如何从另一个对象构造当前对象”。默认拷贝行为编译器会生成一个默认的拷贝构造函数执行浅拷贝逐成员复制对于指针只复制地址不复制指向的数据。4.2 浅拷贝 vs 深拷贝浅拷贝默认拷贝构造器String(const String other) : m_Buffer(other.m_Buffer), m_Size(other.m_Size) {}两个对象的m_Buffer指向同一块堆内存。当一个对象析构delete[]后另一个对象的指针变成悬空指针当第二个对象析构时对同一块内存再次delete导致双重释放程序崩溃。深拷贝可自定义拷贝构造String(const String other) : m_Size(other.m_Size) { m_Buffer new char[m_Size 1]; memcpy(m_Buffer, other.m_Buffer, m_Size 1); }为新对象分配独立的内存完整复制数据内容。两个对象的指针指向不同内存完全独立互不影响析构时各自释放自己的内存。4.3函数传递参数的发式值传递形参是实参的拷⻉函数内部对形参的操作并不会影响到外部的实参。指针传递也是值传递的⼀种⽅式形参是指向实参地址的指针当对形参的指向操作时就相当于对实参本身进⾏操作。引用传递实际上就是把引⽤对象的地址放在了开辟的栈空间中函数内部对形参的任何操作可以直接映射到外部的实参上⾯。4.4 避免隐式拷贝在函数传参时按值传递会调用拷贝构造函数对于大型对象或管理资源的类这种隐式拷贝开销很大且可能引入不必要的深拷贝。使用引用可以避免拷贝提升性能尤其对于大对象const修饰引用保证函数不会修改原对象语义清晰。void PrintString(String s) { // 按值传递拷贝构造 std::cout s std::endl; } String name Cherno; PrintString(name); // 触发 String 的深拷贝即使只读 void PrintString(const String s) { // const 引用不拷贝 std::cout s std::endl; } String name Cherno; PrintString(name); // 无拷贝直接使用原对象何时需要按值传递需要在函数内修改副本且不影响原对象例如对参数进行排序、变换。对于小且简单的类型如int、double、指针按值传递通常比引用更快引用也有开销。

更多文章