C++11 智能指针 shared_ptr
Written on 2023-01-16
std::shared_ptr<T>
共享智能指针,也被称为计数智能指针。
共享智能指针会记录有多少个共享智能指针指向同一个对象,当这个数为 0 的时候,程序自动的默认释放(析构)这个对象,记录有多少个的这个方法叫做引用计数
。
共享智能指针可以有多个共享智能指针同时管理同一个对象。
举个栗子
普通指针管理:
#include <iostream>
#include <memory>
using namespace std;
class Person{
public:
Person(){
cout << "Constructor: person's age = " << m_age << endl;
}
Person(int age){
m_age = age;
cout << "Constructor: person's age = " << m_age << endl;
}
~Person(){
cout << "Destructor: person's age = " << m_age << endl;
}
private:
int m_age = 0;
};
int main()
{
{
Person *p = new Person(18);
}
cout << "Before return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 18
Before return
**/
依输出可见,p
指向的对象并没有被析构,因为没有析构函数打印的数据,这造成了内存泄漏。
shared_ptr
智能指针管理:
int main()
{
{
shared_ptr<Person> sPtr1 {new Person(18)};
}
cout << "Before return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 18
Destructor: person's age = 18
Before return
**/
依输出可见,打印了析构函数的数据,p
指向的对象自动的被析构了。
获取引用计数
long use_count() const noexcept;
当 shared_ptr
为 nullptr
时,返回为 0。
初始化
创建空管理的 shared_ptr
constexpr shared_ptr() noexcept;
constexpr shared_ptr(std::nullptr_t) noexcept;
std::nullptr_t
:空指针nullptr
创建对非数组对象和数组对象管理的 shared_ptr
template< class Y >
explicit shared_ptr( Y* ptr );
Y
:动态分配的对象的类型
例:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
// 创建空管理的shared_ptr
shared_ptr<int> sPtr1;
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
// 创建空管理的shared_ptr
shared_ptr<int> sPtr2(nullptr);
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
// 创建对非数组对象管理的shared_ptr
shared_ptr<int> sPtr3(new int(100));
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
// 创建对数组对象管理的shared_ptr
shared_ptr<int> sPtr4(new int[10]);
cout << "sPtr4 use count = " << sPtr4.use_count();
return 0;
}
/** 输出:
sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 1
sPtr4 use count = 1
**/
解释:
sPtr1
和sPtr2
都为nullptr
,故引用计数都为 0;
sPtr3
和sPtr4
所管理的对象,都只有一个 shared_ptr
管理,故引用计数都为 1。
创建指定删除器的 shared_ptr
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
template< class Deleter >
shared_ptr( std::nullptr_t ptr, Deleter d );
template< class Y, class Deleter, class Alloc >
shared_ptr( Y* ptr, Deleter d, Alloc alloc );
template< class Deleter, class Alloc >
shared_ptr( std::nullptr_t ptr, Deleter d, Alloc alloc );
Deleter
:删除器,在引用计数为 0 时,shared_ptr
会自动的执行删除器Alloc
:分配器(Allocator)
- 默认删除器:默认对指向的内存地址进行释放,即析构掉这块地址的内容
- 非数组类型,以
delete ptr
为默认删除器 - 数组类型,以
delete[] ptr
为默认删除器
- 非数组类型,以
- 指定删除器:能够自行指定引用计数为 0 时,要做什么,比如有些场景不需要对指向的内存进行释放,比如关闭指向文件,关闭指向套接字
- 指定删除器的函数结构:
void deletePtr(T* p) { ... }
- 也可使用 Lambda表达式
- 指定删除器的函数结构:
例,对于文件的使用场景,不是直接delete
文件,而是关闭文件:
void closeFile(FILE* fp)
{
if (fp == nullptr) return;
fclose(fp);
cout << "File closed" << endl;
}
int main()
{
FILE* fp = fopen("data.txt" ,"w");
shared_ptr<FILE> sfp{fp,closeFile};
if (sfp == nullptr)
cout << "Failed opened" << endl;
else
cout << "File opened" << endl;
}
创建通过现有 shared_ptr
的 shared_ptr
主要是通过复制构造函数和移动构造函数。
shared_ptr( const shared_ptr& r ) noexcept;
template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept;
shared_ptr( shared_ptr&& r ) noexcept;
template< class Y >
shared_ptr( shared_ptr<Y>&& r ) noexcept;
例:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sPtr1(new int(100));
cout << "sPtr1 use count = " << sPtr1.use_count() << endl << endl;
// 创建通过现有 shared_ptr 的 shared_ptr
// 通过拷贝构造函数创建
shared_ptr<int> sPtr2(sPtr1);
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl << endl;
// 通过赋值运算符创建
shared_ptr<int> sPtr3 = sPtr1;
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl << endl;
// 通过移动构造函数创建
shared_ptr<int> sPtr4(move(sPtr1));
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
std::shared_ptr<int> sPtr5 = move(sPtr2);
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl;
cout << "sPtr5 use count = " << sPtr5.use_count();
return 0;
}
/** 输出:
sPtr1 use count = 1
sPtr1 use count = 2
sPtr2 use count = 2
sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3
sPtr1 use count = 0
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 3
sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 3
sPtr4 use count = 3
sPtr5 use count = 3
**/
解释:
sPtr2
、sPtr3
都是通过对sPtr1
执行了复制构造函数,因此sPtr1
的引用计数一次增加 1;sPtr2
、sPtr3
同理。
通过move(sPtr1)
,使得sPtr1
释放了被管理对象的所有权,此时sPtr1
被设置为nullptr
,因此sPtr1
引用计数为 0;sPtr2
同理。
通过 std::make_shared
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
T
:非数组,指针指向的数据类型Args&&... args
:T
的构造函数参数列表
这种初始化效率更高,在 C++17 之前的编译器更安全
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sPtr1 = make_shared<int>(100);
shared_ptr<int> sPtr2(make_shared<int>(200));
shared_ptr<int> sPtr3{make_shared<int>(300)};
return 0;
}
通过 std::reset
使用std::reset
,使得释放对shared_ptr
原管理对象的所有权,转为对新对象管理的所有权。
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 );
注意,reset
若传入的所指向的对象已被占有,程序会异常。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sPtr1(new int(100));
shared_ptr<int> sPtr2 = sPtr1;
shared_ptr<int> sPtr3 = sPtr2;
shared_ptr<int> sPtr4 = sPtr1;
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
sPtr4.reset();
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
sPtr3.reset(new int(100));
cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
cout << "sPtr4 use count = " << sPtr4.use_count();
std::shared_ptr<int> sPtr5;
sPtr5.reset(sPtr1.get()); // 异常
return 0;
}
/** 输出:
sPtr1 use count = 4
sPtr2 use count = 4
sPtr3 use count = 4
sPtr4 use count = 4
sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 0
sPtr1 use count = 2
sPtr2 use count = 2
sPtr3 use count = 1
sPtr4 use count = 0
**/
解释:
- 通过复制构造函数后,
sPtr1-4
的引用计数均为 4。 - 对
sPtr4
使用了reset()
后,释放被管理对象的所有权,被管理对象的shared_ptr
个数减 1,sPtr4
被设置为nullptr
,同时,sPtr1-3
的引用计数均变为 3。 - 对
sPtr3
使用了reset(new int(100))
后,释放原被管理对象的所有权,被管理对象的shared_ptr
个数减 1,sPtr4
被设置为新的管理对象int(100)
,同时,sPtr1-3
的引用计数均变为 2。 sPtr5
的reset
传入的所指向的对象已被占有,程序异常,没有正常结束。
reset
释放被管理对象的所有权
用于取消shared_ptr
对管理对象的所有权,当这个对象被shared_ptr
管理的数量为 0,会执行删除器。
使用shared_ptr.reset()
,会使得shared_ptr
设置为nullptr
就官方文档:
#include <memory>
#include <iostream>
struct Foo {
Foo(int n = 0) noexcept : bar(n) {
std::cout << "Foo: constructor, bar = " << bar << '\n';
}
~Foo() {
std::cout << "Foo: destructor, bar = " << bar << '\n';
}
int getBar() const noexcept { return bar; }
private:
int bar;
};
int main()
{
std::shared_ptr<Foo> sptr = std::make_shared<Foo>(1);
std::cout << "The first Foo's bar is " << sptr->getBar() << "\n";
// 重置,交与新的 Foo 实例
// (此调用后将销毁旧实例)
sptr.reset(new Foo);
std::cout << "The second Foo's bar is " << sptr->getBar() << "\n";
}
/** 输出:
Foo: constructor, bar = 1
The first Foo's bar is 1
Foo: constructor, bar = 0
Foo: destructor, bar = 1
The second Foo's bar is 0
Foo: destructor, bar = 0
**/
解释:
当调用sptr.reset(new Foo);
,bar = 1
对象没有shared_ptr
管理,bar = 1
对象被释放(析构);程序运行结束前,析构管理bar = 0
对象的shared_ptr
,bar = 0
对象没有shared_ptr
管理,bar = 0
对象被释放(析构)。
获取原始储存的指针
T* get() const noexcept;
解引用存储的指针 operator* and operator->
可以像普通指针一样,使用shared_ptr
对所管理的对象进行访问
int main()
{
shared_ptr<int> sPtr1 {new int(100)};
cout << *sPtr1 << endl << endl;
int *i = sPtr1.get();
cout << *i << endl;
cout << *sPtr1 << endl << endl;
*i = 200;
cout << *i << endl;
cout << *sPtr1 << endl << endl;
*sPtr1 = 300;
cout << *i << endl;
cout << *sPtr1;
return 0;
}
/** 输出:
100
100
100
200
200
300
300
**/
直观展示自动管理内存
#include <iostream>
#include <memory>
using namespace std;
class Person{
public:
Person(){
cout << "Constructor: person's age = " << m_age << endl;
}
Person(int age){
m_age = age;
cout << "Constructor: person's age = " << m_age << endl;
}
~Person(){
cout << "Destructor: person's age = " << m_age << endl;
}
private:
int m_age = 0;
};
int main()
{
shared_ptr<Person> sPtr1 {make_shared<Person>()};
shared_ptr<Person> sPtr2 {make_shared<Person>(18)};
shared_ptr<Person> sPtr3 {make_shared<Person>(22)};
shared_ptr<Person> sPtr4 = sPtr1;
sPtr1.reset();
sPtr2.reset();
sPtr3.reset(new Person(19));
cout << "Before return" << endl;
return 0;
}
/** 输出:
Constructor: person's age = 0
Constructor: person's age = 18
Constructor: person's age = 22
Destructor: person's age = 18
Constructor: person's age = 19
Destructor: person's age = 22
Before return
Destructor: person's age = 0
Destructor: person's age = 19
**/
解释:
- 定义了三个
shared_ptr
:sPtr1
、sPtr2
、sPtr3
,它们管理的对象分别为age = 0
、age = 18
、age = 22
,打印了三行Person
对象的构造函数中的输出 - 通过赋值运算符,使得
sPtr4
同时管理sPtr1
管理的对象 - 释放
sPtr1
被管理对象的所有权,此时因为sPtr4
还在管理原sPtr1
管理的对象age = 0
,因此age = 0
对象并没有被析构 - 释放
sPtr2
被管理对象的所有权,此时因为age = 18
对象没有任何shared_ptr
进行管理,age = 18
对象被析构,打印了age = 18
对象的析构函数中的输出 - 释放
sPtr3
被管理对象的所有权,sPtr3
转为管理age = 19
的对象;是先构造age = 19
对象,后再释放sPtr3
被管理对象的所有权,打印了age = 19
对象的构造函数中的输出;此时因为sPtr3
原管理的age = 22
对象没有任何shared_ptr
进行管理,age = 22
对象被析构,打印了age = 22
对象的析构函数中的输出 - 在程序返回前
Before return
,管理age = 0
和age = 19
对象的智能指针sPtr4
和sPtr3
被析构,age = 0
和age = 19
对象没有智能指针管理,age = 0
和age = 19
对象被析构,打印了它们对象的析构函数中的输出。
别名
template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;
别名用于访问类的成员变量。
我们需要访问的是某个实例的成员,因此并不希望在使用这个成员的时候,对应的实例被销毁了。
使用别名的shared_ptr
,增加了对这个实例的控制权,但仍然访问的是成员。
struct Person{ int age = 18; };
struct Student{ Person person; };
int main(){
shared_ptr<Student> studentPtr{make_shared<Student>()};
cout << "studentPtr use count = " << studentPtr.use_count() << endl;
shared_ptr<Person> personPtr{studentPtr, &(studentPtr->person)};
cout << "studentPtr use count = " << studentPtr.use_count() << endl;
cout << personPtr->age << endl;
return 0;
}
/** 输出:
studentPtr use count = 1
studentPtr use count = 2
18
**/
shared_ptr
与函数
管理动态数组需要指定删除器
shared_ptr
的默认删除器不支持释放数组对象,需要指定删除器
例,一维数组指定删除器
shared_ptr<int> ptr(new int[10], [](int* p) {delete[] p; });
同时,也可以使用 std::default_delete<T>()
函数作为删除器,这个函数内部的删除功能是通过delete
释放,T 为释放什么类型的内存的类型。
例,一维数组指定删除器
shared_ptr<int> ptr(new int[10], default_delete<int[]>());
可以自己封装模板函数来使 shared_ptr
支持释放数组对象。
#include <iostream>
#include <memory>
using namespace std;
template <typename T>
shared_ptr<T> arrayShared_ptr(size_t size)
{
// 返回匿名对象
return shared_ptr<T>(new T[size], default_delete<T[]>());
}
int main()
{
shared_ptr<int> sPtr1 = arrayShared_ptr<int>(100);
shared_ptr<int> sPtr2 = arrayShared_ptr<char>(200);
return 0;
}
危险行为
仍然可使用delete
释放智能指针管理的对象的地址
可以使用delete
释放shared_ptr
管理的对象的地址,但是其它共享指针仍然可能会访问这块地址,若访问了则会出现程序异常,因此应避免使用手动的delete
。
int main(){
shared_ptr<int> sPtr1 {new int(100)};
shared_ptr<int> sPtr2 = sPtr1;
delete sPtr1.get();
cout << *sPtr2 << endl;
}
同时存在裸指针和智能指针
如果一个地址内存,同时有裸指针和shared_ptr
指向它,即使当所有shared_ptr
都被销毁,裸指针还依然存在的情况下,这个地址内存仍然会被释放,若再用裸指针去访问这个内存地址就是未定义的行为。
int main(){
int *i1 = new int{100};
shared_ptr<int> sPtr1{i};
int *i2 = sPtr1.get();
delete sPtr1;
cout << *i1 << endl;
cout << *i2 << endl;
}
同时,无论是使用shared_ptr
,还是其它的智能指针,都应该避免与裸指针混用。
避免同时存在裸指针和智能指针的解决方案:
int *i = new int{100};
shared_ptr<int> sPtr1{i};
i = nullptr;
delete i;
cout << *sPtr1 << endl;
使用一个裸指针初始化多个shared_ptr
不能使用一个裸指针初始化多个shared_ptr
int * i = new int(100);
shared_ptr<int> sPtr1{i};
shared_ptr<int> sPtr2{i};// error 编译通过 运行错误
最后
shared_ptr
由于使用引用计数,因此会造成额外的内存和性能开销,因此在性能要求极为苛刻的情况下不适用。
unique_ptr
是 0 开销的智能指针,也能够自动管理内存,但不会造成性能损失。