C++11 引入了 智能指针来解决手动管理动态内存的复杂性。它们能够自动管理堆内存,并在不再需要时自动释放,避免内存泄漏和悬空指针问题。C++11 提供了三种主要的智能指针类型:std::unique_ptr、std::shared_ptr 和 std::weak_ptr。
1. std::unique_ptr:独占所有权
 
std::unique_ptr 是一种 独占所有权 的智能指针,它保证同一时间只能有一个智能指针拥有对象的所有权。这意味着对象只能被一个 unique_ptr 所管理,不能被多个指针共享。使用 std::unique_ptr 的场景通常是当一个对象的生命周期只需要在单个作用域内管理时。
特点:
- 独占所有权:不能复制,只能移动。
- 生命周期:当 unique_ptr离开其作用域时,会自动销毁并释放所管理的对象。
- 轻量、高效:由于不涉及引用计数,unique_ptr的效率通常比shared_ptr更高。
示例:
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // 自动管理动态分配的 MyClass// std::unique_ptr<MyClass> ptr2 = ptr1; // 错误,不能复制 unique_ptrstd::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 正确,通过移动所有权// ptr1 现在为空
}重要操作:
- 创建智能指针:使用 std::make_unique<T>()动态分配内存并返回std::unique_ptr<T>,避免手动使用new。
- 移动所有权:使用 std::move进行所有权转移,不能复制。
2. std::shared_ptr:共享所有权
 
std::shared_ptr 实现了 共享所有权,即可以有多个智能指针共同拥有同一个对象。对象的生命周期会被多个 shared_ptr 共享,只有当最后一个 shared_ptr 被销毁时,托管的对象才会被释放。
特点:
- 共享所有权:多个 shared_ptr可以指向同一个对象。
- 引用计数:shared_ptr维护一个引用计数器,当指向对象的所有shared_ptr被销毁时,引用计数器变为 0,对象会自动释放。
- 适用场景:多个对象需要共享同一个资源时,例如在复杂的资源共享或对象关系管理中使用。
示例:
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();{std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 2}// 离开作用域,ptr2 被销毁,引用计数减少为 1std::cout << "Reference count: " << ptr1.use_count() << "\n"; // 输出 1
}重要操作:
- 创建智能指针:使用 std::make_shared<T>()创建共享指针,比手动分配内存效率更高。
- 引用计数:使用 use_count()查看当前引用计数。
注意事项:
- 循环引用问题:如果两个或多个 shared_ptr对象形成循环引用(即 A 指向 B,B 又指向 A),那么即使引用计数变为 0,内存也不会被释放,导致内存泄漏。为了解决这个问题,引入了std::weak_ptr。
3. std::weak_ptr:弱引用
 
std::weak_ptr 是为了防止 std::shared_ptr 之间的 循环引用问题 而设计的。weak_ptr 并不影响引用计数,不拥有对象的所有权。它只是一个观察者,可以访问由 shared_ptr 管理的对象。如果 shared_ptr 管理的对象已经被释放,那么 weak_ptr 变为无效。
特点:
- 不增加引用计数:weak_ptr只是持有对象的引用,不会影响shared_ptr的引用计数。
- 生命周期观察:使用 weak_ptr观察对象的生命周期,可以通过lock()函数将weak_ptr转换为shared_ptr,安全地访问对象。
示例:
#include <memory>
#include <iostream>struct MyClass {MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }
};int main() {std::shared_ptr<MyClass> sptr = std::make_shared<MyClass>();std::weak_ptr<MyClass> wptr = sptr; // weak_ptr 引用 sptr,但不增加引用计数std::cout << "Reference count: " << sptr.use_count() << "\n"; // 输出 1if (auto shared = wptr.lock()) {std::cout << "Object is still alive\n";} else {std::cout << "Object has been destroyed\n";}sptr.reset(); // 释放 shared_ptr 管理的对象if (auto shared = wptr.lock()) {std::cout << "Object is still alive\n";} else {std::cout << "Object has been destroyed\n";}
}shared_ptr 内部维护的关键成员变量:
 
1. 指向对象的原始指针(Managed Object Pointer)
- 这是指向实际动态分配对象的原始指针。它是 shared_ptr管理的对象,shared_ptr使用这个指针来访问对象。
- 例如:T* ptr,它指向动态分配的对象(例如通过make_shared或new分配的对象)。
2. 控制块(Control Block)
- 控制块 是 shared_ptr背后维护的一个数据结构,包含了引用计数、弱引用计数以及其他额外的信息。控制块并不与所管理的对象共享地址,它是shared_ptr机制中的核心,包含多个关键数据:
-  - 引用计数(Reference Counter):
 
-  -  - 一个用于记录有多少个 shared_ptr实例同时引用该对象的计数器,称为共享引用计数。当创建一个新的shared_ptr实例指向相同对象时,该计数器递增;当shared_ptr被销毁或重置时,该计数器递减。
- 当引用计数降为 0 时,托管的对象会被销毁。
 
- 一个用于记录有多少个 
 
-  
-  - 弱引用计数(Weak Reference Counter):
 
-  -  - 另一个计数器,记录有多少个 weak_ptr实例引用该对象。weak_ptr不会影响共享引用计数,但会影响控制块的生命周期。
- 当共享引用计数和弱引用计数都降为 0 时,控制块本身也会被销毁。
 
- 另一个计数器,记录有多少个 
 
-  
-  - 自定义删除器(Custom Deleter):
 
-  -  - shared_ptr可以绑定一个自定义删除器,用于控制对象的销毁方式。如果你不指定删除器,- shared_ptr会使用默认的- delete运算符释放对象。
- 删除器保存在控制块中,当最后一个 shared_ptr离开作用域时,它会调用这个删除器来销毁对象。
 
 
-  
3. 弱引用管理(Weak Pointer Management)
- shared_ptr与- weak_ptr的结合使用涉及到对控制块中弱引用计数的维护。- weak_ptr不会增加对对象的共享引用计数,但会使控制块的弱引用计数增加。这样,即使没有任何- shared_ptr实例指向对象,控制块依然存在,直到所有- weak_ptr也被销毁。
具体控制块结构的解释
可以将控制块理解为包含以下信息的结构体(这是一个概念上的简化):
struct ControlBlock {int shared_count;   // 共享引用计数int weak_count;     // 弱引用计数void(*deleter)(T*); // 自定义删除器函数指针
};每次创建 shared_ptr 时,shared_count 会增加,而 weak_count 仅在创建 weak_ptr 时增加。
控制块与对象的关系
- 独立于对象的存储:控制块是一个独立的数据结构,和对象的内存分配是分开的。当使用 make_shared时,对象和控制块可能会被分配到同一块内存区域中,但它们的作用是不同的。
- 生命周期管理:当 shared_count变为 0 时,shared_ptr管理的对象会被销毁;当weak_count也为 0 时,控制块才会被销毁。
shared_ptr 循环引用的例子:
 
#include <iostream>
#include <memory>struct B;struct A {std::shared_ptr<B> ptrB;  // A持有B的shared_ptr~A() { std::cout << "A destroyed\n"; }
};struct B {std::shared_ptr<A> ptrA;  // B持有A的shared_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;  // A拥有Bb->ptrA = a;  // B拥有A// 循环引用导致A和B永远不会被销毁return 0;
}在这个例子中:
- A持有一个指向- B的- shared_ptr,而- B也持有一个指向- A的- shared_ptr。
- 由于 shared_ptr会增加引用计数,a和b的引用计数永远无法减为 0,导致它们的析构函数不会被调用,从而造成内存泄漏。
解决循环引用:使用 std::weak_ptr
 
要解决循环引用问题,可以将其中一个 shared_ptr 替换为 std::weak_ptr。weak_ptr 不会影响引用计数,因此可以打破循环引用。
解决后的代码:
#include <iostream>
#include <memory>struct B;struct A {std::shared_ptr<B> ptrB;  // A持有B的shared_ptr~A() { std::cout << "A destroyed\n"; }
};struct B {std::weak_ptr<A> ptrA;    // B持有A的weak_ptr,打破循环引用~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;  // A拥有Bb->ptrA = a;  // B弱引用A,不增加引用计数// 正常销毁A和B,输出"A destroyed"和"B destroyed"return 0;
}关键点:
- 将 B对A的引用改为std::weak_ptr。weak_ptr不增加引用计数,不会阻止对象的销毁。
- 在需要使用 weak_ptr指向的对象时,可以调用lock()方法将其转换为shared_ptr,如果对象已被销毁,则返回nullptr。