C++智能指针

C++智能指针

std::shared_ptr 共享指针

C++11 shared_ptr智能指针(超级详细)

b站:C++的共享指针 shared_ptr 如何帮你自动管理内存 C++ 你可能不知道的 shared_ptr 补充知识点

http://www.cppds.com/cpp/memory/shared_ptr.html

比如:std::shared_ptr<wenet::FeaturePipelineConfig> feature_config_ = nullptr;

解释

共享指针会记录有多少个共享指针指针指向同一个物体(或者说对象,object),当这个数字将为0时,程序就会自动释放这个物体,省去我们手动delete的烦恼。

因此用共享指针的好处是可以自动管理内存。用于多个指针指向同一个资源的场景。

因为需要引用计数,有额外开销,因此操作性能(或者说速度)相比于裸指针会一点点下降,可能不适合应用于对性能要求严苛的场景。

写法

要使用智能指针首先需要 #include <memory>,在std命名空间中,所以可以先写上 using namespace std;(这样后面就不用写std::,不然还要注明是 std::

1
2
3
4
5
6
7
8
9
10
11
#include <memory>
using namespace std;


// 定义一个shared_ptr指针
shared_ptr<int> p;
// 初始化这个shared_ptr
p = make_shared<int>(100); //推荐使用make_shared的写法。

// 或者写成
shared_ptr<int> p {new int(100)};

make_shared 会动态分配一块内存,创建对应的资源,然后让shared_ptr指针指向这块内存。

make_shared 接受一个类型和对应的初始化参数。

创建完成后,就可以像普通指针一样使用shared_ptr。

1
2
3
4
5
6
7
8
shared_ptr<int> p2 = p;
cout << *p << endl;
*p2 = 321;
cout << *p << endl;

// 输出
100
321

shared_ptr<int> p2 = p; 是复制shared_prt,让多个指针指向同一个物体,这就是共享。

几种创建方法

  1. 通过如下 2 种方式,可以构造出 shared_ptr 类型的空智能指针:
1
2
std::shared_ptr<int> p1;             //不传入任何实参
std::shared_ptr<int> p2(nullptr); //传入空指针 nullptr

注意,空的 shared_ptr 指针,其初始引用计数为 0,而不是 1。

  1. 在构建 shared_ptr 智能指针,也可以明确其指向。例如:
1
std::shared_ptr<int> p3(new int(10));

由此,我们就成功构建了一个 shared_ptr 智能指针,其指向一块存有 10 这个 int 类型数据的堆内存空间。

同时,C++11 标准中还提供了 std::make_shared 模板函数,其可以用于初始化 shared_ptr 智能指针,例如:

1
std::shared_ptr<int> p3 = std::make_shared<int>(10);

以上 2 种方式创建的 p3 是完全相同。

  1. 除此之外,shared_ptr 模板还提供有相应的拷贝构造函数和移动构造函数,例如:
1
2
3
4
//调用拷贝构造函数
std::shared_ptr<int> p4(p3);//或者 std::shared_ptr<int> p4 = p3;
//调用移动构造函数
std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr<int> p5 = std::move(p4);

如上所示,p3 和 p4 都是 shared_ptr 类型的智能指针,因此可以用 p3 来初始化 p4,由于 p3 是左值,因此会调用拷贝构造函数。需要注意的是,如果 p3 为空智能指针,则 p4 也为空智能指针,其引用计数初始值为 0;反之,则表明 p4 和 p3 指向同一块堆内存,同时该堆空间的引用计数会加 1。

而对于 std::move(p4) 来说,该函数会强制将 p4 转换成对应的右值,因此初始化 p5 调用的是移动构造函数。另外和调用拷贝构造函数不同,用 std::move(p4) 初始化 p5,会使得 p5 拥有了 p4 的堆内存,而 p4 则变成了空智能指针。

注意,同一普通指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常。例如:

1
2
3
int* ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);//错误
  1. 在初始化 shared_ptr 智能指针时,还可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为 0 时,会优先调用我们自定义的释放规则。

在某些场景中,自定义释放规则是很有必要的。比如,对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。

见下面的 “自定义删除函数”。

引用计数

有多少个shared_ptr指向某个物体。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Ball{
public:
Ball() { cout << "A ball appears." << endl;}
~Ball() { cout << "A ball disappears." << endl;}
void Bounce() { cout << "A ball jumps." << endl;}
};

int main(){
shared_ptr<Ball> p = make_shared<Ball>();
// 或者写成:
shared_ptr<Ball> p {shared_ptr<Ball>()};

cout << p.use_count() << endl;
shared_ptr<Ball> p2 = p;
cout << p.use_count() << " " << p2.use_count() << endl;
shared_ptr<Ball> p3 = p2; //或shared_ptr<Ball> p3 = p;
cout << p.use_count() << " " << p2.use_count() << " " << p3.use_count() << endl;
p.reset();
p2.reset();
p3.reset();
p->Bounce(); // 为什么还会输出啊?没有报错
}
// 输出:
1
2 2
3 3 3
A ball disappears.

创建3个共享指针指向同一个ball类。依次调用use_count()查看有多少指针指向这个物体。

p.reset(),shared_ptr调用reset()后会重置,不再指向原来的物体,这里函数内即使不加reset(),函数结束后也会自动释放,也就是调用~Ball()

3个shared_ptr都释放后,没有shared_ptr指向开头的ball,就自动释放。?

  • todo:p.reset();之后再访问p->Bounce();为什么还能打印??

reset()传参数,指向新物体

1
2
3
4
shared_ptr<Ball> sp;
sp = make_shared<Ball>();
sp.reset(new Ball);

sp = reset(new Ball) 表示sp指向一个新ball类对象物体,旧的ball引用计数-1。

自定义删除函数 让shared_ptr释放资源 default_delete

当名叫sfp的shared_ptr引用计数降为0时,执行close_file这个函数

1
2
3
4
5
6
7
8
9
10
11
12
void close_file(FILe* fp){
if(fp == nullptr) return;
fclose(fp)l
cout << "File close" << endl;
}

int main(){
FILE* fp = fopen("data.txt", "w");
shared_ptr<FILE> sfp {fp, close_file}; //sfp指向fp的地址
if(sfp == nullptr)
cerr << "Error opening file" << endl;
}
1
2
3
4
5
6
7
8
//指定 default_delete 作为释放规则
std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());
//自定义释放规则
void deleteInt(int*p) {
delete []p;
}
//初始化智能指针,并自定义释放规则
std::shared_ptr<int> p7(new int[10], deleteInt);

shared_prt得到裸指针

p.get(),比如 Ball* rp = p.get() , 其中p是shared_prt,假设Ball函数接收的参数只能是裸指针(不能接收智能指针),则可以通过调用shared_ptr的 get() 方法,来获取一个裸指针,再指向当前的内存资源。

一块内存资源,同时有智能指针和裸指针指向它时,当所有的智能指针都被摧毁,但是裸指针仍然存在时,此时资源仍然会被释放,此时再用裸指针访问那块资源,会变成未定义的行为,因此此时也就不用释放裸指针了。因此用共享指针时避免和裸指针混用。

aliasing 别名

举例:

1
2
3
4
5
6
7
8
9
struct Bar { int i = 123; };
struct Foo { Bar bar; };
int main(){
shared_ptr<Foo> f = make_shared<Foo>();
cout << f.use_count() << endl; // 输出1
shared_ptr<Bar> b(f, &(f->bar));
cout << f.use_count() << endl; // 输出2
cout << b->i << endl; // 输出123
}

std::unique_ptr 独享指针

b站:C++,为什么你应该尽可能使用unique_ptr替换裸指针*C++如何在函数间传递unique_ptr?

解释

是一种零开销的智能指针,可以实现自动内存管理,也没有额外的性能开销。资源独享,不能复制。

写法

1
2
3
4
#include <memory>
unique_ptr<int> UP = make_unique<int>(100);

unique_ptr<Ball> up {make_unique<Ball>()}

和裸指针类似的用法:

1
2
3
auto ball = make_unique<Ball>();
ball->Bounce();
(*ball).Bounce();

想获得资源的裸指针:

1
Ball* p = up->get();

释放资源:

1
2
3
4
up.reset();

// 释放资源的同时指向另一份资源(新的类实例)
up.reset(new Ball{});

up.reset() 会释放unique_ptr下面的资源,并把该unique_ptr设置为nullptr

资源解绑:

1
2
3
Ball* ball = up.release();
delete ball;
ball = nullptr;

up.release() 返回资源的裸指针,同时把该unique_ptr设置成nullptr。

自定义分配函数和释放函数 decltype 。

函数间传递unique_ptr

函数传参很多时候是传值,需要复制。而unique_ptr不支持复制,因此容易写错。

举例,以下代码会编译错误

1
2
3
4
5
6
7
void pass_up(unique_str p){
cout << *up << endl;
}
void main(){
auto up = unique_str<int>(100);
pass_up(up);
}

调用pass_up(up) 时,会产生一个复制up的操作,这是不允许的。

修改如下:

  1. 访问unique_str的资源

如果不需要传递指针本身,要的仅仅是内容的话:

1
2
3
4
5
6
7
void pass_up(int& value){
cout << value << endl; //引用传递
}
void main(){
auto up = unique_str<int>(100);
pass_up(*up); //指针指向的地址内容
}

或者 传递裸指针(裸指针作为参数传递):

1
2
3
4
5
6
7
void pass_up(int* p){
cout << *p << endl;
}
void main(){
auto up = unique_str<int>(100);
pass_up(up.get()); // 裸指针
}
  1. 通过函数改变unique_str本身

函数的参数设计为unique_ptr的引用

1
2
3
4
5
6
7
8
9
void pass_up(unique_prt<int>& up){
cout << *up << endl; //引用传递
up.reset();
}
void main(){
auto up = unique_str<int>(100);
pass_up(up);
if(up == nullptr) cout << "up is reset" << endl;
}

或者 用 std::move

1
2
3
4
5
6
7
8
void pass_up(unique_prt<int> up){
cout << *up << endl; //引用传递
}
void main(){
auto up = unique_str<int>(100);
pass_up(move(up));
if(up == nullptr) cout << "up is moved" << endl;
}

move 可以转移unique_ptr对资源的控制权。