std::unique_ptr和std::shared_ptr是C++中用于自动管理资源生命周期的智能指针,遵循RAII原则,前者实现独占所有权,后者通过引用计数实现共享所有权,能有效防止内存泄漏和悬空指针。

在C++中,
std::unique_ptr
std::shared_ptr
unique_ptr
shared_ptr
谈到智能指针,我个人觉得这简直是C++迈向“更安全、更现代”的关键一步。以前手动
new
delete
使用 std::unique_ptr
std::unique_ptr
unique_ptr
unique_ptr
立即学习“C++免费学习笔记(深入)”;
它的一个显著特点是不能被复制,但可以被“移动”。移动语义在这里发挥了关键作用,它允许所有权从一个
unique_ptr
#include <iostream>
#include <memory>
#include <vector>
// 假设有一个资源类
class MyResource {
public:
MyResource(int id) : id_(id) {
std::cout << "MyResource " << id_ << " created." << std::endl;
}
~MyResource() {
std::cout << "MyResource " << id_ << " destroyed." << std::endl;
}
void doSomething() {
std::cout << "MyResource " << id_ << " doing something." << std::endl;
}
private:
int id_;
};
void processUniqueResource(std::unique_ptr<MyResource> res) {
if (res) { // 检查是否有效
res->doSomething();
}
// res 在这里超出作用域,MyResource 会被自动销毁
}
int main() {
// 1. 创建 unique_ptr
// 推荐使用 std::make_unique,它更安全,效率更高
auto ptr1 = std::make_unique<MyResource>(1);
ptr1->doSomething();
// 2. 移动所有权
// ptr1 的所有权转移给 ptr2,ptr1 变为 nullptr
auto ptr2 = std::move(ptr1);
if (ptr1) {
std::cout << "This should not print." << std::endl;
}
if (ptr2) {
ptr2->doSomething();
}
// 3. 将 unique_ptr 作为函数参数传递(通过移动)
std::cout << "Calling processUniqueResource..." << std::endl;
processUniqueResource(std::move(ptr2)); // ptr2 再次变为 nullptr
std::cout << "processUniqueResource returned." << std::endl;
// 4. unique_ptr 数组
std::vector<std::unique_ptr<MyResource>> resources;
resources.push_back(std::make_unique<MyResource>(3));
resources.push_back(std::make_unique<MyResource>(4));
// 离开作用域时,vector 中的所有 MyResource 都会被销毁
// main 函数结束时,所有剩余的 unique_ptr 也会自动释放资源
return 0;
}使用 std::shared_ptr
当多个对象需要共享同一个资源的所有权时,
std::shared_ptr
shared_ptr
shared_ptr
这对于实现一些复杂的对象图,或者当资源的生命周期不确定,需要由多个不相关的部分共同管理时,非常有用。
#include <iostream>
#include <memory>
#include <vector>
// 假设有一个资源类
class SharedResource {
public:
SharedResource(int id) : id_(id) {
std::cout << "SharedResource " << id_ << " created." << std::endl;
}
~SharedResource() {
std::cout << "SharedResource " << id_ << " destroyed." << std::endl;
}
void showId() {
std::cout << "SharedResource ID: " << id_ << std::endl;
}
private:
int id_;
};
void observeSharedResource(std::shared_ptr<SharedResource> res) {
std::cout << " Inside observeSharedResource, ref count: " << res.use_count() << std::endl;
res->showId();
// res 在这里超出作用域,引用计数减少
}
int main() {
// 1. 创建 shared_ptr
// 推荐使用 std::make_shared,它更安全,效率更高
auto s_ptr1 = std::make_shared<SharedResource>(10);
std::cout << "Initial ref count: " << s_ptr1.use_count() << std::endl; // 1
// 2. 复制 shared_ptr,共享所有权
auto s_ptr2 = s_ptr1; // 复制,引用计数增加
std::cout << "After copy to s_ptr2, ref count: " << s_ptr1.use_count() << std::endl; // 2
// 3. 传递 shared_ptr 作为函数参数(通过值传递或 const 引用)
observeSharedResource(s_ptr1); // 传递时引用计数会增加1,函数返回时减少1
std::cout << "After observeSharedResource, ref count: " << s_ptr1.use_count() << std::endl; // 2
// 4. 创建另一个 shared_ptr
std::shared_ptr<SharedResource> s_ptr3;
{
auto temp_ptr = std::make_shared<SharedResource>(20);
s_ptr3 = temp_ptr; // 复制,引用计数增加
std::cout << "Inside block, temp_ptr ref count: " << temp_ptr.use_count() << std::endl; // 2
} // temp_ptr 销毁,引用计数减少到 1
std::cout << "After block, s_ptr3 ref count: " << s_ptr3.use_count() << std::endl; // 1
// main 函数结束时,所有 shared_ptr 销毁,引用计数归零,SharedResource 会被销毁
return 0;
}std::unique_ptr
std::shared_ptr
这真的是一个非常基础但又极其重要的问题,很多时候,选错了智能指针,可能会导致不必要的性能开销,甚至引入难以调试的逻辑错误。
核心区别:所有权模型
std::unique_ptr
unique_ptr
unique_ptr
unique_ptr
unique_ptr
std::shared_ptr
shared_ptr
shared_ptr
shared_ptr
何时选择它们?
这其实是一个关于资源语义的问题,我通常是这样思考的:
首选 std::unique_ptr
unique_ptr
std::unique_ptr
unique_ptr
shared_ptr
当需要共享所有权时才使用 std::shared_ptr
shared_ptr
shared_ptr
我个人在写代码时,总是先尝试用
unique_ptr
shared_ptr
unique_ptr
shared_ptr
std::shared_ptr
std::weak_ptr
啊,
shared_ptr
循环引用是如何发生的?
简单来说,就是两个或更多个
shared_ptr
shared_ptr
shared_ptr
shared_ptr
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
A() { std::cout << "A constructed" << std::endl; }
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::shared_ptr<A> a_ptr;
B() { std::cout << "B constructed" << std::endl; }
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
std::cout << "Starting main..." << std::endl;
{
auto a = std::make_shared<A>(); // a.use_count() == 1
auto b = std::make_shared<B>(); // b.use_count() == 1
a->b_ptr = b; // b.use_count() == 2 (A持有B)
b->a_ptr = a; // a.use_count() == 2 (B持有A)
std::cout << "a's ref count: " << a.use_count() << std::endl;
std::cout << "b's ref count: " << b.use_count() << std::endl;
} // a 和 b 在这里超出作用域
std::cout << "Exiting main." << std::endl;
// 预期 A 和 B 都不会被销毁,因为它们的引用计数都停留在 1
// A 的 b_ptr 引用 B,B 的 a_ptr 引用 A
return 0;
}运行上面的代码,你会发现"A destroyed"和"B destroyed"都没有打印出来,这就是循环引用导致的内存泄漏。
std::weak_ptr
std::weak_ptr
shared_ptr
shared_ptr
当
weak_ptr
shared_ptr
weak_ptr::lock()
shared_ptr
shared_ptr
lock()
shared_ptr
如何使用 std::weak_ptr
核心思想是:在可能形成循环引用的关系中,将其中一方的
shared_ptr
weak_ptr
weak_ptr
以上面的A和B为例,如果我们将B对A的引用改为
weak_ptr
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
A() { std::cout << "A constructed" << std::endl; }
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // 这里改为 weak_ptr
B() { std::cout << "B constructed" << std::endl; }
~B() { std::cout << "B destroyed" << std::endl; }
void accessA() {
if (auto sharedA = a_ptr.lock()) { // 尝试获取 shared_ptr
std::cout << "B is accessing A." << std::endl;
// 可以在这里使用 sharedA
} else {
std::cout << "A has been destroyed." << std::endl;
}
}
};
int main() {
std::cout << "Starting main..." << std::endl;
{
auto a = std::make_shared<A>(); // a.use_count() == 1
auto b = std::make_shared<B>(); // b.use_count() == 1
a->b_ptr = b; // b.use_count() == 2 (A持有B)
b->a_ptr = a; // a_ptr 是 weak_ptr,不增加 A 的引用计数,a.use_count() 仍然是 1
std::cout << "a's ref count: " << a.use_count() << std::endl; // 1
std::cout << "b's ref count: " << b.use_count() << std::endl; // 2
b->accessA(); // B 仍然可以访问 A
} // a 和 b 在这里超出作用域
std::cout << "Exiting main." << std::endl;
// 此时,A 和 B 都将正常销毁
return 0;
}这次运行,你会看到"A destroyed"和"B destroyed"都正常打印了。当外部
shared_ptr a
A
A
A
b_ptr
B
shared_ptr b
B
B
选择使用
weak_ptr
智能指针的威力远不止于内存管理,这是它们设计理念中非常优雅和强大的一面。
std::unique_ptr
std::shared_ptr
这完全符合RAII(Resource Acquisition Is Initialization)的精髓:资源在构造时获取,在析构时释放。智能指针就是这个“析构时释放”的完美载体。
智能指针可以管理的非内存资源示例:
FILE*
HANDLE
SOCKET
pthread_mutex_t*
HANDLE
dlopen
如何实现自定义资源管理(自定义删除器)?
自定义删除器是一个可调用对象(函数、函数对象、Lambda表达式),它接收一个指向资源的原始指针作为参数,并负责释放该资源。
对于 std::unique_ptr
自定义删除器是其模板参数的一部分。这意味着删除器的类型会影响
unique_ptr
#include <iostream>
#include <memory>
#include <cstdio> // For FILE* and fclose
// 1. 使用函数作为删除器
void closeFile(FILE* filePtr) {
if (filePtr) {
std::cout << "Closing file using function deleter." << std::endl;
fclose(filePtr);
}
}
// 2. 使用 Lambda 表达式作为删除器(更常见和灵活)
// Lambda 通常是无状态的,或者捕获少量变量
auto customFileDeleter = [](FILE* filePtr) {
if (filePtr) {
std::cout << "Closing file using lambda deleter." << std::endl;
fclose(filePtr);
}
};
int main() {
// 示例1: 管理文件句柄,使用函数作为删除器
// 注意 unique_ptr 的第二个模板参数是删除器的类型
std::unique_ptr<FILE, decltype(&closeFile)> file1(fopen("test1.txt", "w"), &closeFile);
if (file1) {
fprintf(file1.get(), "Hello from file1!\n");
std::cout << "File1 opened successfully." << std::endl;
} else {
std::cerr << "Failed to open test1.txt" << std::endl;
}
// file1 超出作用域时,closeFile 会被调用
// 示例2: 管理文件句柄,使用 Lambda 作为删除器
// decltype(customFileDeleter) 会自动推导出 Lambda 的类型
std::unique_ptr<FILE, decltype(customFileDeleter)> file2(fopen("test2.txt", "w"), customFileDeleter);
if (file2) {
fprintf(file2.get(), "Hello from file2!\n");
std::cout << "File2 opened successfully." << std::endl;
} else {以上就是C++如何使用std::unique_ptr和std::shared_ptr管理资源的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号