std::shared_ptr
是 C++11 引入的智能指针类型之一,它管理动态分配的对象,并自动在不再需要时释放该对象,避免了手动管理内存的麻烦。下面将对 shared_ptr
进行详细讲解,包括其初始化、基本使用、应用场景、demo 以及注意事项。
1. std::shared_ptr
简介
shared_ptr
是一种引用计数智能指针,多个 shared_ptr
对象可以共享同一个资源。当最后一个 shared_ptr
不再引用该资源时,资源会被自动释放。适用于需要多个对象共享同一资源的场景。
2. 初始化与基本使用
初始化
可以通过 std::make_shared
或直接构造 shared_ptr
对象来进行初始化:
#include <iostream>
#include <memory>
int main() {
// 使用 make_shared 初始化
std::shared_ptr<int> sp1 = std::make_shared<int>(100);
// 直接构造 shared_ptr
std::shared_ptr<int> sp2(new int(200));
std::cout << "sp1: " << *sp1 << std::endl;
std::cout << "sp2: " << *sp2 << std::endl;
return 0;
}
std::make_shared
通常是更推荐的做法,因为它更高效且能避免内存泄漏。
基本使用
- 获取引用计数:使用
use_count()
获取当前资源的引用计数。 - 访问资源:使用
*
解引用,或者使用->
访问资源的成员。 - 释放资源:当所有的
shared_ptr
都不再持有该资源时,资源会被自动释放。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::shared_ptr<int> sp2 = sp1; // 引用计数加1
std::cout << "sp1: " << *sp1 << ", 引用计数: " << sp1.use_count() << std::endl;
std::cout << "sp2: " << *sp2 << ", 引用计数: " << sp2.use_count() << std::endl;
sp2.reset(); // sp2 释放,引用计数减1
std::cout << "sp1: " << *sp1 << ", 引用计数: " << sp1.use_count() << std::endl;
return 0;
}
3. 应用场景
- 资源共享:当多个对象需要共享一个动态分配的资源时,例如多个模块共享同一个配置文件、数据结构等。
- 避免内存泄漏:通过自动管理内存释放,避免手动调用
delete
带来的内存泄漏问题。 - 多线程场景:可以安全地跨多个线程共享资源,
shared_ptr
本身是线程安全的。
4. 示例 Demo
下面是一个简单的 demo,展示了如何在多个函数中共享同一资源:
#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> sp) {
std::cout << "foo 中的值: " << *sp << ", 引用计数: " << sp.use_count() << std::endl;
}
void bar(std::shared_ptr<int> sp) {
std::cout << "bar 中的值: " << *sp << ", 引用计数: " << sp.use_count() << std::endl;
}
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(100);
std::cout << "main 中的引用计数: " << sp.use_count() << std::endl;
foo(sp);
bar(sp);
std::cout << "main 中的引用计数: " << sp.use_count() << std::endl;
return 0;
}
输出:
main 中的引用计数: 1
foo 中的值: 100, 引用计数: 2
bar 中的值: 100, 引用计数: 2
main 中的引用计数: 1
5. 注意事项与常见坑
1. 循环引用问题
shared_ptr
的一个常见陷阱是循环引用,当两个或多个对象互相持有对方的 shared_ptr
时,引用计数永远不会为 0,导致资源永远不会释放,造成内存泄漏。解决方案是使用 std::weak_ptr
打破循环引用。
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "Node 析构" << std::endl; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
// 循环引用
node1->next = node2;
node2->next = node1;
// 资源不会被释放,造成内存泄漏
return 0;
}
使用 weak_ptr
可以解决这个问题:
struct Node {
std::weak_ptr<Node> next;
~Node() { std::cout << "Node 析构" << std::endl; }
};
2. 性能开销
shared_ptr
使用了引用计数机制,每次拷贝都会导致引用计数的增加和减少,频繁使用时可能带来一定的性能开销。在性能敏感的场合,可以考虑使用 std::unique_ptr
,它没有引用计数的开销。
3. 避免混合使用裸指针
不要混合使用 shared_ptr
和裸指针,特别是在传递和管理资源时。因为 shared_ptr
是自动管理内存的,如果传入一个裸指针,很容易造成重复释放资源或内存泄漏。
6. 使用细节
1. std::move
与所有权转移
std::shared_ptr
支持通过 std::move
将所有权从一个 shared_ptr
对象转移到另一个对象中,类似于其他智能指针。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp1 = std::make_shared<int>(100);
std::shared_ptr<int> sp2 = std::move(sp1); // sp1 所有权转移给 sp2
if (!sp1) {
std::cout << "sp1 为空" << std::endl;
}
std::cout << "sp2 的值: " << *sp2 << std::endl;
return 0;
}
通过 std::move
,可以将 shared_ptr
的所有权从一个对象转移到另一个对象,而不会增加引用计数。此操作特别适合于需要临时拥有对象的函数或容器。
unique_ptr
转换为 shared_ptr
std::unique_ptr
不能直接复制或共享资源,但可以将其转换为 shared_ptr
,使其所有权转移给 shared_ptr
。这种转换主要适用于那些资源最初只被单独拥有,后来需要共享的场景。
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> up = std::make_unique<int>(100);
std::shared_ptr<int> sp = std::move(up); // 将 unique_ptr 转换为 shared_ptr
std::cout << "sp 的值: " << *sp << std::endl;
return 0;
}
2.reset()
改变与资源的关联
shared_ptr::reset()
用于解除当前 shared_ptr
与其管理资源的关联。调用 reset()
后,原资源的引用计数减1,当引用计数为0时,资源会被释放。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(100);
std::cout << "sp 的引用计数: " << sp.use_count() << std::endl;
sp.reset(); // 解除与资源的关系
std::cout << "sp 解除关联后: " << (sp ? "有资源" : "没有资源") << std::endl;
sp.reset(new int(200)); // 关联新的资源
std::cout << "新资源的值: " << *sp << std::endl;
return 0;
}
3.swap()
交换两个 shared_ptr
shared_ptr::swap()
函数用于交换两个 shared_ptr
对象管理的资源。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp1 = std::make_shared<int>(100);
std::shared_ptr<int> sp2 = std::make_shared<int>(200);
std::cout << "交换前 sp1 的值: " << *sp1 << ", sp2 的值: " << *sp2 << std::endl;
sp1.swap(sp2); // 交换 sp1 和 sp2 管理的资源
std::cout << "交换后 sp1 的值: " << *sp1 << ", sp2 的值: " << *sp2 << std::endl;
return 0;
}
4. 多态支持
shared_ptr
同样支持多态行为。当 shared_ptr
管理基类指针时,若该指针指向派生类对象,可以通过基类指针调用派生类对象的虚函数。
#include <iostream>
#include <memory>
class Base {
public:
virtual void show() { std::cout << "Base 类" << std::endl; }
virtual ~Base() = default;
};
class Derived : public Base {
public:
void show() override { std::cout << "Derived 类" << std::endl; }
};
int main() {
std::shared_ptr<Base> basePtr = std::make_shared<Derived>(); // shared_ptr 指向派生类对象
basePtr->show(); // 调用派生类的函数
return 0;
}
5.exit()
调用时的资源释放
调用 exit()
会立即终止程序的执行,这会导致局部 shared_ptr
无法正常释放其管理的资源。需要注意避免程序在存在未释放的局部资源的情况下意外调用 exit()
。全局或静态 shared_ptr
会在程序退出时被自动释放。
6. 数组支持
shared_ptr
还支持管理动态数组,通过 shared_ptr
管理数组时,可以使用 operator[]
来访问数组元素。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int[]> arr(new int[5]{1, 2, 3, 4, 5});
arr[2] = 10; // 修改数组中的元素
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
7. 线程安全性
std::shared_ptr
的引用计数操作是线程安全的,因为引用计数是原子操作。在多个线程中只进行读取操作时是线程安全的,但如果多个线程同时对同一个 shared_ptr
对象进行读写,或者同时访问 shared_ptr
指向的资源,则需要加锁保护。
#include <iostream>
#include <memory>
#include <thread>
void thread_func(std::shared_ptr<int> sp) {
std::cout << "线程中共享的值: " << *sp << std::endl;
}
int main() {
auto sp = std::make_shared<int>(42);
std::thread t1(thread_func, sp);
std::thread t2(thread_func, sp);
t1.join();
t2.join();
return 0;
}
8.使用 unique_ptr
替代 shared_ptr
如果一个资源只需要由一个对象管理,没有共享的需求,推荐使用 std::unique_ptr
,因为它没有引用计数的开销,效率更高,占用的内存资源也更少。
std::unique_ptr
不能复制,但可以转移所有权,适合用于局部资源管理和简单场景。如果在多个模块中共享资源时,可以考虑使用 shared_ptr
。
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> up = std::make_unique<int>(100);
std::cout << "unique_ptr 的值: " << *up << std::endl;
// std::unique_ptr<int> up2 = up; // 错误,不能复制 unique_ptr
std::unique_ptr<int> up2 = std::move(up); // 转移所有权
std::cout << "转移后 up2 的值: " << *up2 << std::endl;
return 0;
}
总结
std::shared_ptr
提供了强大的内存管理功能,特别适合资源共享的场景。它有许多高级特性,如支持 std::move
、reset()
、swap()
、多态行为、线程安全等。在使用时需要注意避免循环引用,并根据场景选择更高效的 unique_ptr
。