13.c++11的智能指针有哪些。weak_ptr的使用场景。什么情况下会产生循环引用

2026-01-23
2278 字 · 11 分钟

🔬 C++11智能指针详解:weak_ptr与循环引用

📖 内容概览

  • 了解C++11中三种智能指针的特点和使用场景
  • 掌握weak_ptr的核心原理和应用
  • 理解循环引用的产生原因
  • 学习如何使用weak_ptr解决循环引用问题
  • 学会智能指针的最佳实践

🔍 C++11智能指针类型

C++11引入了三种智能指针,用于自动管理动态内存,避免内存泄漏:

智能指针类型所有权模型核心特点适用场景
unique_ptr独占所有权不可拷贝,可移动
零开销抽象
自动释放资源
单一对象独占资源
工厂函数返回值
容器元素
shared_ptr共享所有权引用计数
线程安全引用计数
可拷贝,可移动
多个对象共享资源
长期存在的资源
跨作用域共享
weak_ptr弱引用不影响引用计数
需lock()获取shared_ptr
可检测对象是否存活
解决循环引用
观察者模式
缓存和资源管理

🔄 智能指针详解

🔒 unique_ptr

核心特点

  • 独占资源所有权,同一时间只能有一个unique_ptr指向同一对象
  • 不可拷贝,但可以通过std::move()转移所有权
  • 析构时自动释放资源
  • 支持自定义删除器
  • 零额外开销(与裸指针性能相同) 使用示例
#include <memory>
#include <iostream>
int main() {
// 创建unique_ptr
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// 转移所有权
std::unique_ptr<int> p2 = std::move(p1);
// p1现在为空
if (!p1) {
std::cout << "p1 is null" << std::endl;
}
// p2持有资源
std::cout << *p2 << std::endl; // 输出: 42
return 0; // p2自动释放资源
}

🤝 shared_ptr

  • 共享资源所有权,多个shared_ptr可以指向同一对象
  • 内部维护引用计数,记录有多少个shared_ptr指向同一对象
  • 当引用计数为0时,自动释放资源
  • 引用计数是线程安全的
  • 有一定的性能开销(引用计数的原子操作)
void func(std::shared_ptr<int> p) {
std::cout << "引用计数: " << p.use_count() << std::endl;
}
int main() {
// 创建shared_ptr
std::shared_ptr<int> p1 = std::make_shared<int>(100);
std::cout << "引用计数: " << p1.use_count() << std::endl; // 输出: 1
// 拷贝shared_ptr,引用计数增加
std::shared_ptr<int> p2 = p1;
std::cout << "引用计数: " << p1.use_count() << std::endl; // 输出: 2
// 函数传参,引用计数增加
func(p1); // 输出: 3
return 0; // p1和p2销毁,引用计数变为0,资源释放
}

🔗 weak_ptr

  • 弱引用,不影响引用计数
  • 不能直接访问对象,需要通过lock()方法获取shared_ptr
  • 可以检测对象是否还存活
  • 用于解决shared_ptr的循环引用问题
#include <memory>
#include <iostream>
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(50);
std::weak_ptr<int> wp = sp; // 弱引用,引用计数不变
std::cout << "shared_ptr引用计数: " << sp.use_count() << std::endl; // 输出: 1
// 通过lock()获取shared_ptr
if (auto p = wp.lock()) {
std::cout << "对象值: " << *p << std::endl; // 输出: 50
std::cout << "lock后引用计数: " << sp.use_count() << std::endl; // 输出: 2
}
// 销毁shared_ptr
sp.reset();
// 检测对象是否存活
if (auto p = wp.lock()) {
std::cout << "对象仍然存活" << std::endl;
} else {
std::cout << "对象已被销毁" << std::endl; // 输出
}
return 0;
}

🔬 weak_ptr的使用场景

🛠️ 解决循环引用

问题:两个或多个对象互相持有shared_ptr,导致引用计数永远不为0,内存无法释放 解决方案:将其中一个shared_ptr改为weak_ptr

🔄 观察者模式

应用:观察者对象持有被观察对象的弱引用,避免影响被观察对象的生命周期 示例

class Subject {
std::vector<std::weak_ptr<Observer>> observers;
public:
void addObserver(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
void notify() {
for (auto& wp : observers) {
if (auto sp = wp.lock()) {
sp->update();
}
}
};

💾 缓存和资源管理

应用:缓存对象时,使用weak_ptr避免缓存对象影响原对象的生命周期

class Cache {
std::unordered_map<std::string, std::weak_ptr<Resource>> cache;
std::shared_ptr<Resource> get(const std::string& key) {
if (auto it = cache.find(key); it != cache.end()) {
if (auto resource = it->second.lock()) {
return resource;
// 对象已被销毁,从缓存中移除
cache.erase(it);
// 创建新资源并缓存
auto resource = std::make_shared<Resource>(key);
cache[key] = resource;
return resource;
}
};

⚠️ 循环引用的产生

🧩 循环引用示例

#include <memory>
#include <iostream>
class B; // 前置声明
class A {
public:
std::shared_ptr<B> b; // A持有B的shared_ptr
~A() {
std::cout << "A被销毁" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a; // B持有A的shared_ptr
~B() {
std::cout << "B被销毁" << std::endl;
}
};
int main() {
{ // 新作用域
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
// 互相引用,形成循环
a->b = b;
b->a = a;
std::cout << "a引用计数: " << a.use_count() << std::endl; // 输出: 2
std::cout << "b引用计数: " << b.use_count() << std::endl; // 输出: 2
} // 作用域结束,a和b销毁
// 但A和B的析构函数不会被调用!
std::cout << "作用域结束,检查是否内存泄漏" << std::endl;
return 0;
}

输出结果: a引用计数: 2 b引用计数: 2 作用域结束,检查是否内存泄漏 问题分析

  • ab离开作用域时,它们的引用计数从2减为1
  • 但A和B对象仍然互相持有对方的shared_ptr,引用计数永远不会减为0
  • 因此A和B的析构函数不会被调用,内存永远不会释放,造成内存泄漏

💡 使用weak_ptr解决循环引用

🛠️ 解决方案

将其中一个shared_ptr改为weak_ptr,打破循环引用:

#include <memory>
#include <iostream>
class B; // 前置声明
class A {
public:
std::shared_ptr<B> b; // A仍然持有B的shared_ptr
~A() {
std::cout << "A被销毁" << std::endl;
}
};
class B {
public:
std::weak_ptr<A> a; // B持有A的weak_ptr,不影响引用计数
~B() {
std::cout << "B被销毁" << std::endl;
}
};
int main() {
{ // 新作用域
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b = b;
b->a = a; // 弱引用,a的引用计数仍为1
std::cout << "a引用计数: " << a.use_count() << std::endl; // 输出: 1
std::cout << "b引用计数: " << b.use_count() << std::endl; // 输出: 2
} // 作用域结束,a和b销毁
// A和B的析构函数都会被调用!
std::cout << "作用域结束,内存正常释放" << std::endl;
return 0;
}

输出结果: a引用计数: 1 b引用计数: 2 A被销毁 B被销毁 作用域结束,内存正常释放 解决方案分析

  1. B类使用weak_ptr<A>而不是shared_ptr<A>
  2. a离开作用域时,其引用计数从1减为0,A对象被销毁
  3. A对象销毁时,会自动销毁其成员b,导致B对象的引用计数从2减为1
  4. b离开作用域时,其引用计数从1减为0,B对象被销毁
  5. 循环引用被打破,内存正常释放

🛠️ 最佳实践

  1. 优先使用unique_ptr
    • 对于独占资源,优先使用unique_ptr,它是最高效的智能指针
    • 只有当需要共享资源时,才使用shared_ptr
  2. 使用make_unique和make_shared
    • 更安全:避免内存泄漏(当创建对象和赋值之间发生异常时)
    • 更高效:make_shared只分配一次内存(对象和引用计数一起)
    • 更简洁:代码更易读
  3. 避免循环引用
    • 设计类时,注意避免互相持有shared_ptr
    • 必要时使用weak_ptr打破循环
  4. 不要混合使用裸指针和智能指针
    • 避免将裸指针赋值给智能指针
    • 避免从智能指针获取裸指针后长期持有
  5. 选择合适的智能指针
    • 短暂使用的资源:unique_ptr
    • 长期共享的资源:shared_ptr
    • 解决循环引用:weak_ptr
  6. 考虑自定义删除器
    • 对于特殊资源(如文件句柄、网络连接),可以为智能指针指定自定义删除器

📊 性能对比

智能指针类型内存开销拷贝开销线程安全性适用场景
unique_ptr与裸指针相同禁止拷贝,移动开销小不涉及共享,线程安全独占资源
shared_ptr额外的引用计数原子操作,开销较大引用计数线程安全共享资源
weak_ptr额外的控制块指针拷贝开销小引用计数线程安全解决循环引用

📋 总结

  1. C++11智能指针类型
    • unique_ptr:独占所有权,零开销,不可拷贝
    • shared_ptr:共享所有权,引用计数,线程安全
    • weak_ptr:弱引用,不影响引用计数,解决循环引用
  2. weak_ptr的核心作用
    • 解决shared_ptr的循环引用问题
    • 实现观察者模式
    • 用于缓存和资源管理
    • 检测对象是否存活
  3. 循环引用的产生
    • 两个或多个对象互相持有shared_ptr
    • 导致引用计数永远不为0,内存无法释放
    • 是shared_ptr最常见的陷阱
  4. 解决方案
    • 将其中一个shared_ptr改为weak_ptr
    • 打破循环引用,让引用计数能正常减为0
  5. 最佳实践
    • 优先使用unique_ptr
    • 使用make_unique和make_shared
    • 避免循环引用
    • 不要混合使用裸指针和智能指针 通过合理使用C++11智能指针,可以有效避免内存泄漏,编写更安全、更可靠的C++代码。理解每种智能指针的特点和适用场景,是掌握现代C++内存管理的关键。

Thanks for reading!

13.c++11的智能指针有哪些。weak_ptr的使用场景。什么情况下会产生循环引用

2026-01-23
2278 字 · 11 分钟

已复制链接

评论区

目录