C++ 智能指针
在C++中,智能指针是一种管理动态分配的内存的手段,它们可以自动释放不再使用的内存,避免了内存泄漏的问题。智能指针属于C++标准库中的<memory>头文件。C++11引入了三种智能指针:std::unique_ptr、std::shared_ptr和std::weak_ptr。
1. 为什么需要智能指针?
在传统 C++ 中,使用 new 和 delete 手动管理内存容易出现问题:
-
内存泄漏:忘记
delete。 -
野指针:
delete后未能将指针置nullptr,再次使用。 -
重复释放:对同一块内存
delete多次。
使用智能指针的好处:
-
自动内存管理:减少内存泄漏的风险。
-
简化代码:不需要手动调用delete。
-
异常安全:智能指针确保即使发生异常也能正确释放资源。
-
避免裸露的指针:减少使用裸露的指针(raw pointers),提高代码的安全性和可维护性。
智能指针通过 RAII (Resource Acquisition Is Initialization) 技术来解决这些问题:
-
将内存资源封装在对象中。
-
在构造函数中获取资源(分配内存)。
-
在析构函数中释放资源(释放内存)。
-
利用栈对象离开作用域自动析构的特性,确保资源一定能被释放。
2. C++ 标准库提供的智能指针
标准库在 <memory> 头文件中提供了三种主要的智能指针:
| 智能指针 | 所有权模型 | 用途 |
|---|---|---|
std::unique_ptr | 独占所有权 | 管理不需要共享的资源,性能开销最小。 |
std::shared_ptr | 共享所有权 | 管理需要多个使用者共享的资源。 |
std::weak_ptr | 弱引用 | 配合 shared_ptr 使用,解决循环引用问题。 |
std::auto_ptr | (已废弃) | C++98 提供,有严重缺陷,绝对不要使用。 |
3. std::unique_ptr:独占的智能指针
unique_ptr 独占其所指对象的所有权。同一时刻只能有一个 unique_ptr 指向一个给定对象。
-
无法被拷贝,只能被移动 (
std::move)。 -
开销极小,几乎和裸指针一样。
-
是
std::auto_ptr的替代品。
创建与使用
cpp
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed\n"; }
~MyClass() { std::cout << "MyClass destroyed\n"; }
void doSomething() { std::cout << "Doing something...\n"; }
};
int main() {
// 1. 创建 unique_ptr
std::unique_ptr<MyClass> ptr1(new MyClass()); // 方式一
auto ptr2 = std::make_unique<MyClass>(); // 方式二(C++14推荐)
// 2. 使用,就像普通指针一样
ptr2->doSomething();
(*ptr2).doSomething();
// 3. 释放资源(显式,通常不需要)
// ptr2.reset(); // 释放 ptr2 指向的对象,ptr2 变为 nullptr
// 4. 转移所有权(移动语义)
std::unique_ptr<MyClass> ptr3 = std::move(ptr2); // ptr2 现在为 nullptr
if (!ptr2) {
std::cout << "ptr2 is now empty\n";
}
// 5. 获取原始指针(谨慎使用!)
MyClass* raw_ptr = ptr3.get();
// MyClass* raw_ptr2 = ptr3.release(); // 释放所有权,返回指针,你需要手动delete
return 0;
} // ptr1 和 ptr3 离开作用域,自动调用析构函数释放内存
std::make_unique (C++14) 的优势:
-
异常安全。
-
只需要写一次类型,避免代码重复。
-
性能可能稍好。
4. std::shared_ptr:共享的智能指针
shared_ptr 通过引用计数实现共享所有权。多个 shared_ptr 可以指向同一个对象。
-
当最后一个指向对象的
shared_ptr被销毁或重置时,对象才会被销毁。 -
开销比
unique_ptr大(需要维护引用计数和控制块)。
创建与使用
cpp
#include <memory>
#include <iostream>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main() {
// 1. 创建 shared_ptr
std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>(); // 推荐方式
{
std::cout << "Inside inner scope:\n";
// 2. 拷贝构造,引用计数+1
std::shared_ptr<Resource> ptr2 = ptr1;
std::cout << "Use count: " << ptr2.use_count() << std::endl; // 输出 2
// ptr2 离开作用域,引用计数-1
}
std::cout << "Back to outer scope:\n";
std::cout << "Use count: " << ptr1.use_count() << std::endl; // 输出 1
// 3. 重置
ptr1.reset(); // 释放资源,ptr1 变为 nullptr
std::cout << "Use count after reset: " << ptr1.use_count() << std::endl; // 输出 0
return 0;
}
std::make_shared 的优势:
-
异常安全。
-
更高效率:通常只需一次内存分配(将对象和控制块分配在一起)。
5. std::weak_ptr:弱引用的智能指针
weak_ptr 是为了解决 shared_ptr 的循环引用问题而设计的。
-
它不控制对象的生命周期,即不会增加引用计数。
-
它观察一个由
shared_ptr管理的对象。 -
需要通过
lock()方法获取一个可用的shared_ptr来临时访问资源。
循环引用问题与解决方案
cpp
#include <memory>
#include <iostream>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed\n"; }
};
class B {
public:
// std::shared_ptr<A> a_ptr; // 循环引用!导致内存泄漏!
std::weak_ptr<A> a_ptr; // 解决方案:使用 weak_ptr
~B() { std::cout << "B destroyed\n"; }
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b; // A 强引用 B
b->a_ptr = a; // B 弱引用 A,不会增加引用计数
// 离开作用域后,a 的引用计数先归零,A 被销毁。
// 然后 A 的析构会使 b_ptr 析构,B 的引用计数归零,B 被销毁。
return 0;
}
使用 weak_ptr::lock()
cpp
void useResource(std::weak_ptr<MyClass> weak_ptr) {
// 尝试获取一个临时的 shared_ptr
if (auto shared_temp = weak_ptr.lock()) { // 如果对象还存在
shared_temp->doSomething(); // 安全地使用资源
std::cout << "Use count during access: " << shared_temp.use_count() << std::endl;
} else {
std::cout << "Resource is no longer available.\n";
}
// shared_temp 离开作用域,临时引用计数减少
}
6. 如何选择智能指针?
遵循这个简单的决策流程:
-
是否需要共享所有权?
-
否 -> 使用
std::unique_ptr。这是默认的首选,开销最小。-
是否需要转移所有权?使用
std::move()。
-
-
是 -> 使用
std::shared_ptr。
-
-
使用
shared_ptr时,是否有循环引用的风险?-
是 -> 将引用链中的一环改为
std::weak_ptr。 -
否 -> 直接使用
std::shared_ptr。
-
7. 最佳实践与注意事项
-
优先使用
make_unique和make_shared:它们更安全、更高效。 -
不要手动管理资源:尽量避免使用
new和delete。 -
不要将同一块原始指针交给多个智能指针:这会导致重复释放。
cpp
-
MyClass* raw_ptr = new MyClass(); std::unique_ptr<MyClass> p1(raw_ptr); // std::unique_ptr<MyClass> p2(raw_ptr); // 灾难!会重复释放!
-
谨慎使用
get()获取原始指针:绝对不要用这个指针去创建另一个智能指针或手动delete它。 -
按值传递智能指针:函数参数传递时,按值传递
shared_ptr表示函数要共享所有权(会增加引用计数)。如果函数只是使用对象,应传递原始指针或引用 (T*,T&)。
更多推荐

所有评论(0)