我们知道,new
在堆上分配内存,需要delete
来删除内存,因为其不会自动的释放内存。然而智能指针(smart pointer)能够自动化的实现这一过程。
当调用new
的时候,不再需要调用delete
,甚至在很多使用智能指针的情况不需要使用new
。
智能指针在本质上是对原始指针的包装:
当创建一个智能指针的时候,会调用new
,并且分配内存,然后基于使用的智能指针的类型,到达条件时自动释放内存(调用delete
)。
unique_ptr
unique_ptr是一个作用域指针scope pointer,当这个指针超出作用域时,会被销毁,调用delete。
必须是唯一的,不能被复制,如果有两个unique_ptr指针指向同一块内存,其中一个释放,指向相同内存的另一个unique_ptr会指向一个已经被释放的内存。存在的意义就在于,保证这个指针的唯一性,只能有这一个指针指向对应的内存地址。
std::unique_ptr
是一种独占型的智能指针,表示所管理的对象只能由一个指针拥有。无法通过复制来共享其管理权,因此它的所有权只能通过转移(move semantics)来传递。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructor" << std::endl; }
~MyClass() { std::cout << "MyClass Destructor" << std::endl; }
};
int main() {
// 创建一个 unique_ptr 管理 MyClass 对象
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
// 转移所有权
std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
//unique_prt不能被复制,拷贝构造函数和拷贝构造操作符被删除了
//std::unique_ptr<MyClass> en = ptr1;
// ptr1 现在为空,ptr2 拥有对象
if (!ptr1) {
std::cout << "ptr1 is null" << std::endl;
}
return 0;
}
这是最简单的智能指针,非常有用且开销低(甚至没有开销),它只是一个栈分配对象,当栈分配对象死亡(销毁)时,它将在你的指针上调用delete ,并释放内存。前面提到了unique_ptr 不能被复制。如果你去看它的定义,你会发现它的拷贝构造函数和拷贝构造操作符实际上被删除了,这就是为什么你运行如下代码时会编译错误。
note: copy constructor is implicitly deleted because 'unique_ptr<MyClass>' has a user-declared move constructor
unique_ptr(unique_ptr&& __u) _NOEXCEPT
这是专门用来防止自掘坟墓的,因为你不能复制这个:其中一个指针被释放后,这个堆分配对象的底层内存会被释放。(意思就是说它其实不是指针而是个对象)所有指向这块堆内存的指针都将悬空。
所以如果你想“共享”这个指针,这就是shared pointer(共享指针)的用处所在了。
特性:
- 独占所有权:std::unique_ptr 不能复制,但可以通过 std::move 转移所有权。
- 轻量级:由于它的独占性,std::unique_ptr 的开销比 std::shared_ptr 小。
shared pointer
std::shared_ptr
是一种共享所有权的智能指针,多个指针可以共同拥有一个对象。当最后一个 shared_ptr
被销毁时,所管理的对象才会被释放。它内部维护了一个引用计数(reference count)来记录有多少 shared_ptr
指向同一个对象。跟踪你的指针有多少个引用。一旦引用计数达到0,它就被删除了。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructor" << std::endl; }
~MyClass() { std::cout << "MyClass Destructor" << std::endl; }
};
int main() {
// 创建一个 shared_ptr 管理 MyClass 对象
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
{
// ptr2 和 ptr1 共享对象的所有权
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
}// ptr2 离开作用域,引用计数减少
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
return 0;
}
特性:
- 共享所有权:可以有多个 shared_ptr 指向同一个对象,只有当所有的 shared_ptr 被销毁时,对象才会被释放。
- 引用计数:每个 shared_ptr 都会增加引用计数,保证安全的内存释放。
- 适合复杂对象生命周期管理:例如多个对象可能共享一个资源时使用。
weak_ptr
std::weak_ptr
是一种不影响对象生命周期的智能指针。它配合 std::shared_ptr
使用,用于解决循环引用的问题。当 weak_ptr
指向的对象已经被销毁时,weak_ptr
不会访问它,也不会增加引用计数。
当仅仅是对指针对象进行操作,不参与对象的生命周期管理,不需要所有权时,可以std::shared_ptr
赋值给std::weak_ptr
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructor" << std::endl; }
~MyClass() { std::cout << "MyClass Destructor" << std::endl; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weakPtr = ptr1; // 不增加引用计数
std::cout << ptr1.use_count() << std::endl;//当前shared_ptr引用个数
//一种常见的使用 std::weak_ptr 的方式,
//它的目的是检查由 std::weak_ptr 所指向的对象是否仍然存在,并安全地获取其共享所有权。
//使用 auto 关键字自动推导变量类型。
//这里,auto 将推导出 sharedPtr 的类型为 std::shared_ptr<T>,T 是 weakPtr 所指向的对象类型。
//lock() 返回的 std::shared_ptr 可以隐式转换为 bool。
//如果 sharedPtr 不为空(即对象仍然存在),则 if 条件为真;如果对象已经被销毁,sharedPtr 为空,条件为假。
if (auto sharedPtr = weakPtr.lock()) {
std::cout << ptr1.use_count() << std::endl;//当前shared_ptr引用个数
std::cout << "Object is still alive" << std::endl;
} else {
std::cout << "Object has been destroyed" << std::endl;
}
return 0;
}
weak_ptr
的使用:std::weak_ptr
没有直接访问所指向对象的能力,因为它不增加引用计数。为了访问所指向的对象,必须通过 lock()
方法。这会返回一个 std::shared_ptr
,如果对象还存在,lock()
会成功获取该对象的共享所有权。如果对象已经被销毁,lock()
返回一个空的std::shared_ptr
。
特性:
- 不参与引用计数:weak_ptr 不增加 shared_ptr 的引用计数,因此不会影响对象的生命周期。
- 避免循环引用:当两个对象互相持有 shared_ptr 时,可能会导致循环引用问题,weak_ptr 可以打破这个循环。
使用场景总结
std::unique_ptr
:适用于确保独占访问资源的场景,简单高效,自动释放内存。std::shared_ptr
:适用于资源需要在多个对象之间共享的场景,它提供自动化的内存管理。std::weak_ptr
:适用于防止循环引用或弱观察者模式,它不影响对象的生命周期。
通过使用智能指针,C++ 程序员可以更方便地管理动态内存的分配和释放,从而减少内存泄漏的风险并编写更稳定的代码。