0%

C++11标准 智能指针

原文链接:C++11标准 智能指针

介绍

智能指针能够自动管理内存,主要是为了更好的维护对象内存分配和回收的. 最大的作用就是防止内存泄露.

发生内存泄漏最主要的原因就是程序中存在异常,并且程序没有堆内存实现异常处理.

内存泄漏

一般情况

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
28
29
#include <iostream>
#include <stdexcept>

class A{
public:
A(){std::cout<<"construct A\n";};
~A(){std::cout<<"destory A\n";};
};

void causeMemoryLeak() {
A* a=new A();
// delete a;//正确位置
throw std::runtime_error("An error occurred");
delete a;

}

int main() {
try {
causeMemoryLeak();
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() <<"\n";
}

return 0;
}

// construct A
// Exception caught: An error occurred

大多数情况中,只要管理好堆上创建的对象,及时delete删除就不会出现内存泄漏.

但是当程序出现异常时,内存还没回收,此时程序就会发送内存泄漏.

如示例中,A被构造了,但是程序结束前delete a没有被执行.

因此发生内存泄漏最主要的原因就是程序中存在异常,并且程序没有堆内存实现异常处理.

异常处理存在的问题!

上面情况出现内存泄漏最大的问题就是异常处理的范围错误.

causeMemoryLeak()内部本身是一个正常的对象分配回收流程,但是runtime_error的异常处理被放在了程序外部,
外部异常处理无法获取函数内的对象.

因此delete应该放在runtime_error前,或者局部函数内不要使用堆内存.

使用智能指针

我们将new A使用
std::shared_ptr a=std::make_shared();
替代,即使出现异常,也能正常析构.

并且shared_ptr对象可以理解为是A的指针.

标准库

根据场景不同,主要包括3种指针:

shared_ptr: 可被线程共享的指针,可任意操作,但是可能会死锁
weak_ptr: 只读的指针,能避免死锁问题
unique_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
//类
template< class T > class shared_ptr;

//构造
// make_shared能减少内存分配次数,将控制块和对象同时分配
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args ); // T是对象,args是参数,返回shared_ptr<T>指针
//复制构造
shared_ptr(const shared_ptr& other);
// 移动构造
shared_ptr(const shared_ptr&& other);

// 获取对象,返回的是对象的指针,如果T本身就是指针,那(*T)是原指针对象
T* get()

// 共享计数
long use_count()

// 判断该对象是否是唯一的
bool unique()

//释放当前对象,指向新对象, 如果无参数,则等价于直接释放
void reset(T* ptr = nullptr);

//交换两个指针的内容
swap(shared_ptr& other) noexcept;

实例:两个智能指针相互指向,导致死锁

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
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <stdexcept>
#include <memory>

class B;
class A{
public:
A(){std::cout<<"construct A\n";};
~A(){std::cout<<"destory A\n";};
std::shared_ptr<B> ptr;

};

class B{
public:
B(){std::cout<<"construct B\n";};
~B(){std::cout<<"destory B\n";};
std::shared_ptr<A> ptr;

};

int main() {
std::shared_ptr<A> a=std::make_shared<A>();
std::shared_ptr<B> b=std::make_shared<B>();
std::cout<<"A.ptr.count:"<<a.use_count()<<" B.ptr.count:"<<b.use_count()<<"\n";
a->ptr=b;
std::cout<<"A.ptr.count:"<<a.use_count()<<" B.ptr.count:"<<b.use_count()<<"\n";
b->ptr=a;
std::cout<<"A.ptr.count:"<<a.use_count()<<" B.ptr.count:"<<b.use_count()<<"\n";

return 0;
}
// construct A
// construct B
// A.ptr.count:1 B.ptr.count:1
// A.ptr.count:1 B.ptr.count:2
// A.ptr.count:2 B.ptr.count:2

weak_ptr

weak_ptr主要是用于解决循环引用问题,weak_ptr本身不影响引用计数.

weak_ptr没有实现*和->操作,表示weak_ptr不允许直接操作对象

主要有几个特殊操作,weak不影响引用计数,同时可以通过lock()操作对象

1
2
3
4
5
6
7
8
9
10
11

weak_ptr可以使用shared_ptr构造共享资源.
weak_ptr(const shared_ptr<T>& r) noexcept;

// 判断该指针对象是否无效
bool expired() const noexcept;
return false(仍然有效) true(对象被销毁了)

// 获取对象的shared_ptr
shared_ptr<T> lock() const noexcept;
return 指针(对象有效) nullptr(对象无效)

实例:weak_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
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <stdexcept>
#include <memory>

class B;
class A{
public:
A(){std::cout<<"construct A\n";};
~A(){std::cout<<"destory A\n";};
std::weak_ptr<B> ptr;

};

class B{
public:
B(){std::cout<<"construct B\n";};
~B(){std::cout<<"destory B\n";};
std::weak_ptr<A> ptr;

};

int main() {
std::shared_ptr<A> a=std::make_shared<A>();
std::shared_ptr<B> b=std::make_shared<B>();
std::cout<<"A.ptr.count:"<<a.use_count()<<" B.ptr.count:"<<b.use_count()<<"\n";
a->ptr=b;
std::cout<<"A.ptr.count:"<<a.use_count()<<" B.ptr.count:"<<b.use_count()<<"\n";
b->ptr=a;
std::cout<<"A.ptr.count:"<<a.use_count()<<" B.ptr.count:"<<b.use_count()<<"\n";

return 0;
}
// construct A
// construct B
// A.ptr.count:1 B.ptr.count:1
// A.ptr.count:1 B.ptr.count:1
// A.ptr.count:1 B.ptr.count:1
// destory B
// destory A

unique_ptr

unique_ptr主要使用于不需要共享的,独占对象的指针.例如一些不能共享的对象.

1
2
3
4
5
6
7
8
9
10
// 重置unique_ptr
void reset(T* ptr = nullptr);

// 释放对象,并返回该对象指针
T* release();

// get操作
T* get()

unique_ptr不需要引用计数,因此没有use_count函数