shared_ptr 智能指针
shared_ptr 智能指针
文章目录
- shared_ptr 智能指针
- 为什么要引入智能指针
- 智能指针的作用
- C++ 中的智能指针种类
- std::shared_ptr
- 使用 `std::make_shared` 创建 `shared_ptr`(推荐方式)
- 使用 `shared_ptr` 的拷贝或移动构造
- 通过构造函数初始化
- 通过reset方法初始化
- 获取原始指针
- 指定删除器
- 使用 `std::shared_ptr` 的注意事项
- 循环引用
- 使用 `std::weak_ptr` 解决循环引用
为什么要引入智能指针
在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
智能指针的作用
- 自动管理动态内存:智能指针通过 RAII(资源获取即初始化)机制自动释放内存。RAII 是 C++ 的核心设计理念之一,资源的生命周期与对象的生命周期绑定,从而确保资源不会被泄漏。
- 避免内存泄漏:智能指针通过引用计数或其他机制,确保内存能够在不再使用时自动释放。
- 避免悬挂指针和野指针:智能指针能够在其不再使用时自动销毁,避免了手动删除后指针仍然存在的危险。
- 确保异常安全:智能指针通过作用域控制内存,确保即使发生异常,也能正确释放资源。
- 简化代码:相比传统指针手动管理内存,智能指针大大简化了内存管理代码,提升了程序的可维护性和可靠性。
C++ 中的智能指针种类
std::unique_ptr
- 用途:表示独占所有权的智能指针,意味着一个
unique_ptr
只能拥有一个对象的所有权。不能被复制,只能移动。 - 特点
- 独占所有权:同一个对象只能有一个
unique_ptr
管理,避免了多重释放的问题。 - 不可拷贝:不能通过拷贝构造函数或赋值操作符来复制,确保了对象的唯一所有权。
- 可以转移所有权:可以通过
std::move
将unique_ptr
的所有权从一个指针转移到另一个。
- 独占所有权:同一个对象只能有一个
- 典型用法
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权
std::shared_ptr
- 用途:表示共享所有权的智能指针,多个
shared_ptr
可以共同拥有同一个对象的所有权。 - 特点
- 引用计数:
shared_ptr
使用引用计数的机制来跟踪有多少个shared_ptr
指向同一对象。当最后一个指向对象的shared_ptr
被销毁时,内存会自动释放。 - 可以拷贝和赋值:
shared_ptr
可以通过拷贝构造和赋值操作符进行拷贝,因此它适用于多个地方共享资源的情况。 - 线程安全:
shared_ptr
在修改引用计数时是线程安全的,但对象本身的操作仍需要额外的同步机制。
- 引用计数:
- 典型用法
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1; // 引用计数增加,两个指针共享所有权
std::weak_ptr
- 用途:用于解决
shared_ptr
引用计数环的问题。weak_ptr
并不增加引用计数,只是观察shared_ptr
指向的对象。 - 特点
- 不增加引用计数:
weak_ptr
不会增加shared_ptr
的引用计数,因此不会影响对象的生命周期。 - **可以转换为 **
shared_ptr
:如果对象仍然存在,weak_ptr
可以通过lock()
方法转换为shared_ptr
,从而访问对象。 - 防止循环引用:在
shared_ptr
之间形成循环引用时,weak_ptr
可以用来打破这种循环,避免内存泄漏。
- 不增加引用计数:
- 典型用法
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::weak_ptr<int> weakPtr = ptr1; // 不增加引用计数
if (auto ptr2 = weakPtr.lock()) { // 如果对象还存在,获得一个 shared_ptr
// 使用 ptr2
}
std::shared_ptr
使用 std::make_shared
创建 shared_ptr
(推荐方式)
std::make_shared 是创建 std::shared_ptr 的推荐方法,它同时分配 shared_ptr 和对象的内存,能够提高效率并减少内存分配次数。
使用std::make_shared()模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void say_hello() { std::cout << "Hello, World!\n"; }
};
int main() {
// 使用 std::make_shared 创建 shared_ptr
std::shared_ptr<MyClass> p1 = std::make_shared<MyClass>();
p1->say_hello(); // 调用成员函数
// 不需要手动释放内存,shared_ptr 会自动销毁对象
return 0;
}
----- 输出 -----
MyClass constructor
Hello, World!
MyClass destructor
使用 shared_ptr
的拷贝或移动构造
如果已有一个 shared_ptr,可以通过拷贝或移动构造一个新的 shared_ptr,共享同一个对象。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor\n"; }
~MyClass() { std::cout << "MyClass destructor\n"; }
void say_hello() { std::cout << "Hello, World!\n"; }
};
int main() {
// 创建 shared_ptr
std::shared_ptr<MyClass> p1 = std::make_shared<MyClass>();
// 拷贝构造
std::shared_ptr<MyClass> p2 = p1;
// 移动构造
std::shared_ptr<MyClass> p3 = std::move(p1);
p2->say_hello();
p3->say_hello(); // p1 已经为空,p3 拥有资源
return 0;
}
通过构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);
// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;
#include <iostream>
#include <memory>
using namespace std;
int main()
{
// 使用智能指针管理一块 int 型的堆内存
shared_ptr<int> ptr1(new int(520));
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
// 使用智能指针管理一块字符数组对应的堆内存
shared_ptr<char> ptr2(new char[12]);
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
// 创建智能指针对象, 不管理任何内存
shared_ptr<int> ptr3;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
// 创建智能指针对象, 初始化为空
shared_ptr<int> ptr4(nullptr);
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
return 0;
}
----- 输出:
ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 1
ptr3管理的内存引用计数: 0
ptr4管理的内存引用计数: 0
如果智能指针被初始化了一块有效内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数不会+1。另外,不要使用一个原始指针初始化多个shared_ptr。
int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); // error, 编译不会报错, 运行会出错
通过reset方法初始化
共享智能指针类提供的std::shared_ptr::reset方法函数原型如下:
void reset() noexcept;
template< class Y >
void reset( Y* ptr );
template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );
template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );
ptr
:指向要取得所有权的对象的指针。调用此reset
后,会接管这个裸指针的所有权,原本的管理对象会被删除。d
: 删除器用于删除ptr
指向的对象。允许你在重置时自定义对象的销毁方式。Alloc
: 是一个自定义分配器,它控制内存的分配和释放,通常用于自定义内存管理场景。
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
shared_ptr<int> ptr1 = make_shared<int>(520);
shared_ptr<int> ptr2 = ptr1;
shared_ptr<int> ptr3 = ptr1;
shared_ptr<int> ptr4 = ptr1;
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
ptr4.reset();
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
shared_ptr<int> ptr5;
ptr5.reset(new int(250));
cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
return 0;
}
-----输出:
ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4
ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0
ptr5管理的内存引用计数: 1
对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。
获取原始指针
通过智能指针可以管理一个普通变量或者对象的地址,此时原始地址就不可见了。当我们想要修改变量或者对象中的值的时候,就需要从智能指针对象中先取出数据的原始内存的地址再操作,解决方案是调用共享智能指针类提供的get()方法,其函数原型如下:
T* get() const noexcept;
指定删除器
std::shared_ptr 允许使用指定的删除器(Deleter)来定制对象销毁的方式。删除器是一个函数对象、函数指针或任何具有 operator() 的对象,可以在 shared_ptr 被销毁时,执行自定义的删除操作。
原型如下:
std::shared_ptr<T> ptr(p, Deleter deleter);
1 > p 是裸指针,shared_ptr 将负责管理它的生命周期。
2 > Deleter 是一个删除器,它在 shared_ptr 被销毁时被调用,用来销毁管理的对象。
使用:
#include <iostream>
#include <memory>
void custom_deleter(int* ptr) {
std::cout << "Custom deleter deleting pointer: " << *ptr << std::endl;
delete ptr; // 使用 delete 进行销毁
}
int main() {
// 创建一个 shared_ptr,传入裸指针和自定义删除器
std::shared_ptr<int> ptr(new int(42), custom_deleter);
// 使用 ptr 进行操作
std::cout << "Shared pointer holds: " << *ptr << std::endl;
/*
// 使用 lambda 表达式作为删除器
auto deleter = [](int* ptr) {
std::cout << "Lambda deleter deleting pointer: " << *ptr << std::endl;
delete ptr;
};
std::shared_ptr<int> ptr(new int(100), deleter);
std::cout << "Shared pointer holds: " << *ptr << std::endl;
*/
// 当 ptr 超出作用域时,custom_deleter 会被调用
return 0;
}
----- 输出 ----
Shared pointer holds: 42
Custom deleter deleting pointer: 42
使用 std::shared_ptr
的注意事项
循环引用
std::shared_ptr 会通过引用计数来管理对象的生命周期,如果两个 shared_ptr 相互持有对方,形成循环引用,那么它们的引用计数永远不会减少,导致内存泄漏。
#include <iostream>
#include <memory>
class A; // 前向声明
class B {
public:
std::shared_ptr<A> ptrA;
B() { std::cout << "B created\n"; }
~B() { std::cout << "B destroyed\n"; }
};
class A {
public:
std::shared_ptr<B> ptrB;
A() { std::cout << "A created\n"; }
~A() { std::cout << "A destroyed\n"; }
};
int main() {
std::shared_ptr<A> pA = std::make_shared<A>();
std::shared_ptr<B> pB = std::make_shared<B>();
pA->ptrB = pB;
pB->ptrA = pA;
// pA 和 pB 互相持有对方,导致循环引用,内存不会被释放
return 0;
}
这个程序不会输出 “A destroyed” 和 “B destroyed”。这是因为 pA 和 pB 互相持有对方,引用计数永远不会降到 0,导致内存泄漏。
使用 std::weak_ptr
解决循环引用
为了避免循环引用,可以使用 std::weak_ptr。std::weak_ptr 不增加引用计数,它允许 shared_ptr 相互引用,但不会影响对象的生命周期。~
#include <iostream>
#include <memory>
class A; // 前向声明
class B {
public:
std::weak_ptr<A> ptrA; // 使用 weak_ptr
B() { std::cout << "B created\n"; }
~B() { std::cout << "B destroyed\n"; }
};
class A {
public:
std::shared_ptr<B> ptrB;
A() { std::cout << "A created\n"; }
~A() { std::cout << "A destroyed\n"; }
};
int main() {
std::shared_ptr<A> pA = std::make_shared<A>();
std::shared_ptr<B> pB = std::make_shared<B>();
pA->ptrB = pB;
pB->ptrA = pA;
// 使用 weak_ptr 解决循环引用
return 0;
}
----------------输出
A created
B created
A destroyed
B destroyed