unique_ptr
unique_ptr
是C++中一种智能指针,它代表对某资源的唯一所有权,确保资源在其生命周期结束时被自动删除,有效防止内存泄漏。
特点:
独占所有权:unique_ptr
==确保同一时间内只有一个智能指针拥有对资源的所有权。==这意味着你不能复制(copy)一个unique_ptr
,企图让两个unique_ptr
共享同一个对象,这有助于避免资源的多重释放问题。
自动资源管理:当unique_ptr
离开作用域时,它会自动删除(通过调用delete
)所拥有的对象,从而防止内存泄漏。这种机制遵循RAII(Resource Acquisition Is Initialization)原则。
可移动性:虽然unique_ptr
不能被复制,但它可以被移动(move)。移动操作转移资源的所有权,使得资源能够有效地在函数间或者数据结构间传递,而不需要显式地释放和重新分配内存。
禁止拷贝,允许移动:通过禁用拷贝构造函数和赋值运算符,unique_ptr
强制执行独占所有权的原则,但提供了移动构造函数和移动赋值运算符,允许资源的所有权安全地转移。
综上所述,unique_ptr
设计的核心目标是提供一种简单而安全的独占资源所有权管理方式,特别适用于那些需要明确资源拥有者并且避免资源共享的情景。
以下是使用unique_ptr
的基本步骤和一些典型操作:
1. 包含头文件
要使用unique_ptr
,首先需要包含<memory>
头文件。
#include <memory>
2. 创建unique_ptr
创建unique_ptr
有两种常见方式:直接构造和使用std::make_unique
函数。
直接构造
std::unique_ptr<int> ptr(new int(42)); // 自C++11起
使用make_unique(推荐,自C++14起)
std::unique_ptr<int> ptr = std::make_unique<int>(42);
3. 访问和修改所指向的对象
直接通过箭头操作符(->
)或解引用操作符(*
)来访问和修改对象。
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// 使用解引用运算符(*)来访问和修改值
std::cout << "*ptr 的值: " << *ptr << std::endl;
*ptr = 17; // 修改值
std::cout << "修改后 *ptr 的值: " << *ptr << std::endl;
// 或者使用箭头运算符(->)来调用成员函数或访问成员变量(如果是指向对象的指针)
// 假设是某个类的对象,比如一个拥有name成员变量的类Person
std::unique_ptr<Person> personPtr = std::make_unique<Person>("Alice");
std::cout << "personPtr 的名字: " << personPtr->getName() << std::endl;
// 注意:这里使用->是因为假设Person类有getName成员函数
// 实际上,对于int或其他基本类型,箭头运算符用于访问不存在的成员是没有意义的
注意:上例中的value
应替换为实际成员变量名,这里仅为示例。
4. 转移所有权
由于unique_ptr
不允许拷贝,但支持移动(转移所有权)。
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int> ptr2 = std::move(ptr1); // 移动所有权给ptr2
// 现在ptr1不再拥有任何资源
5. 释放资源
你可以使用reset()
方法来释放当前unique_ptr
所持有的资源,并可选择性地指向另一个新资源。
ptr.reset(new int(77)); // 释放原资源并重新分配
// 或
ptr.reset(); // 仅释放资源,不分配新资源
6. 获取原始指针
使用get()
方法可以获取unique_ptr
内部存储的原始指针,但需谨慎使用,确保不会导致资源泄露。
int* rawPtr = ptr.get();
7. 析构
当unique_ptr
离开作用域时,它会自动删除所拥有的对象,无需手动调用析构函数。
{
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
// ...使用ptr
} // ptr在此处自动析构,MyClass实例被删除
小结
unique_ptr
提供了一种安全且高效的内存管理方式,通过禁止拷贝和自动管理资源生命周期,有助于编写更加健壮和易于维护的C代码。掌握其基本用法对于现代C编程至关重要。
shared_ptr
std::shared_ptr
是C++标准库提供的智能指针之一,它允许多个指针共享同一个对象的所有权,并且当最后一个指向该对象的shared_ptr
销毁时,该对象会被自动删除,从而有效地管理动态分配的内存,防止内存泄漏。下面是如何使用std::shared_ptr
的基本指南:
1. 引入头文件
首先,你需要包含<memory>
头文件来使用std::shared_ptr
。
#include <memory>
2. 创建shared_ptr
- 直接构造:通过new表达式和构造函数创建。
std::shared_ptr<int> ptr(new int(42));
- 使用make_shared:推荐使用
std::make_shared
,因为它能优化内存分配,通常只有一个内存分配动作。
std::shared_ptr<int> ptr = std::make_shared<int>(42);
3. 共享所有权
多个shared_ptr
可以指向相同的对象,共享其所有权。
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1; // ptr2共享ptr1指向的对象
4. 访问对象
通过解引用(*
)或箭头(->
)操作符访问对象。
*ptr = 100; // 修改值
std::cout << "Value: " << *ptr << std::endl; // 输出值
5. 释放资源
当所有指向对象的shared_ptr
销毁或被重新赋值时,对象将被自动删除。
6. 引用计数
shared_ptr
内部维护一个引用计数,用来追踪有多少个shared_ptr
共享同一对象。可以通过非标准方法查看引用计数(虽然不建议直接操作),但这依赖于具体实现。
void shared_ptr_f()
{
std::shared_ptr<int> sp1 = std::make_shared<int>(42);
std::cout << "sp1's use count: " << sp1.use_count() << std::endl; // 输出应该是 1
std::shared_ptr<int> sp2 = sp1;
std::cout << "After sp2 = sp1, sp1's use count: " << sp1.use_count() << std::endl; // 输出应该是 2
std::cout << "sp2's use count: " << sp2.use_count() << std::endl; // 输出也是 2,因为它们共享同一对象
sp2.reset();
std::cout << "After sp2.reset(), sp1's use count: " << sp1.use_count() << std::endl; // 输出应该是 1,因为sp2不再指向该对象
}
7. 使用自定义删除器
std::shared_ptr
允许指定一个删除器函数或函数对象,用于定制化对象的删除方式。
void customDeleter(int* p) {
delete p;
std::cout << "Custom deletion!" << std::endl;
}
std::shared_ptr<int> ptrWithCustomDel(new int(42), customDeleter);
8. 转换为unique_ptr
虽然不常见,但可以通过转移所有权的方式将shared_ptr
转换为unique_ptr
。
std::unique_ptr<int> uniquePtr = std::move(ptr);
注意事项
- 循环引用:当
shared_ptr
形成循环引用时,可能导致对象无法被释放。这时应考虑使用std::weak_ptr
来打破循环。
- 性能:
shared_ptr
相比原始指针或unique_ptr
有额外的开销,因为它需要维护引用计数。
通过上述指南,您可以开始在项目中安全且有效地使用std::shared_ptr
来管理动态分配的资源。
weak_ptr
std::weak_ptr
是C++智能指针家族的一员,它是一种非拥有型的智能指针,主要用于解决由std::shared_ptr
可能引发的循环引用问题。weak_ptr
本身并不增加它所指向的对象的引用计数,因此,即使所有相关的shared_ptr
销毁,通过weak_ptr
仍然可以观测对象是否存在,而不会阻止对象的释放。
例子:
class Node final {
public:
using this_type = Node;
// 注意这里,别名改用weak_ptr
using shared_type = std::weak_ptr<this_type>;
public:
shared_type next; // 因为用了别名,所以代码不需要改动
};
auto n1 = std::make_shared<Node>(); // 工厂函数创建智能指针
auto n2 = std::make_shared<Node>(); // 工厂函数创建智能指针
n1->next = n2; // 两个节点互指,形成了循环引用
n2->next = n1;
assert(n1.use_count() == 1); // 因为使用了weak_ptr,引用计数为1
assert(n2.use_count() == 1); // 打破循环引用,不会导致内存泄漏
if (!n1->next.expired()) { // 检查指针是否有效
auto ptr = n1->next.lock(); // lock()获取shared_ptr
assert(ptr == n2);
}
PS:
expired()
方法是 std::weak_ptr
类的一个成员函数,它的作用是用来检测其所指向的对象是否已经被销毁。当 weak_ptr
指向的对象没有任何对应的 shared_ptr
引用(即引用计数为0),那么对象会被删除,此时 weak_ptr
就被认为是“过期的”(expired)。
具体来说,当你调用 expired()
函数时:
- 如果
weak_ptr
指向的对象已经被释放,此函数返回true
,表示这个weak_ptr
是无效的或“过期的”。
- 如果对象仍然存在(即至少有一个
shared_ptr
指向它),函数返回false
,表示weak_ptr
仍然是有效的。
这个方法非常重要,尤其是在处理可能形成循环引用的情景下,通过 weak_ptr
可以安全地观察对象是否存在,而又不增加对象的引用计数,从而避免了不必要的生命周期延长。当需要实际访问对象时,可以先用 expired()
检查对象是否还在,然后使用 lock()
方法尝试获取一个临时的 shared_ptr
来安全访问对象。如果对象已不存在(expired()
返回 true
),则 lock()
会返回一个空的 shared_ptr
。
以下是使用std::weak_ptr
的基本指南:
1. 引入头文件
和std::shared_ptr
一样,使用std::weak_ptr
也需要包含<memory>
头文件。
#include <memory>
2. 创建weak_ptr
通常,weak_ptr
是从一个shared_ptr
实例创建的,它不改变源shared_ptr
的引用计数。
std::shared_ptr<int> shared(new int(42));
std::weak_ptr<int> weak = shared; // 创建一个weak_ptr,观察shared指向的对象
3. 检查对象有效性
由于weak_ptr
不控制对象生命周期,访问对象之前必须检查对象是否还存在。
if(auto sharedLocked = weak.lock()) { // 尝试锁定对象
// 对象仍然存在,现在可以安全使用sharedLocked
std::cout << "Value: " << *sharedLocked << std::endl;
} else {
// 对象已被销毁
std::cout << "Object no longer exists." << std::endl;
}
4. 锁定对象(lock())
通过lock()
方法,可以临时获得一个指向对象的shared_ptr
,这会增加引用计数。此shared_ptr
的生命周期结束后,引用计数会相应减少。
std::shared_ptr<int> tempSharedPtr = weak.lock(); // 锁定对象,获取shared_ptr
5. 更新weak_ptr
如果原始shared_ptr
的内容改变了,可以通过再次从新的shared_ptr
实例创建weak_ptr
来更新它。
注意事项
- 循环引用:当两个或更多的
shared_ptr
相互引用形成循环时,可能导致内存泄漏。使用weak_ptr
来引用其中一个或多个对象,可以避免这种循环引用。
- 不直接管理资源:
weak_ptr
不能直接用于删除它所指向的对象,它只是观察者,只有通过lock()
转换为shared_ptr
后,才能访问和管理资源。
- 定期检查:如果你的代码逻辑依赖于
weak_ptr
所观察的对象存在,应该定期检查对象的有效性,以避免对已释放资源的访问。
通过上述指南,您可以有效地利用std::weak_ptr
来增强程序的内存管理机制,尤其是在处理复杂数据结构和避免循环引用方面。
自己实现一个智能指针
等待更新