什么是RAII?
RAII是Resource Acquisition Is Initialization的简称,是C++语言的一种管理资源、避免泄漏的惯用法。
RAII又叫做资源分配即初始化,即:定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
为什么要使用RAII?
- 申请资源;
- 使用资源;
- 释放资源。
第一步和第三步缺一不可,因为资源必须要申请才能使用的,使用完成以后,必须要释放,如果不释放的话,就会造成资源泄漏。
什么是智能指针?
所谓智能指针就是智能/自动化的管理指针所指向的动态资源的释放。它是一个类,有类似指针的功能。
智能指针的实现原理
- 一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;
- 另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。
常见的智能指针
包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr
auto_ptr 独占所有权,转移所有权
第一种实现:最开始auto_ptr的成员变量主要有T* _ptr 和 bool _owner,主要实现原理是在构造对象时赋予其管理空间的所有权,在拷贝或赋值中转移空间的所有权,在析构函数中当_owner为true(拥有所有权)时来释放所有权。
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
template
<
typename
T>
class
AutoPtr
{
public
:
//构造函数
explicit
AutoPtr(T* ptr = NULL)
:_ptr(ptr)
, _owner(
true
)
{}
//拷贝构造
AutoPtr(AutoPtr<T>& ap)
//参数不能写成const的,这里要修改ap对象的成员<br> :_ptr(ap._ptr)
, _owner(
true
)
{
ap._owner =
false
;
//转让权限
}
//赋值运算符重载
AutoPtr& operator=(AutoPtr<T>& ap)
{
if
(
this
! = &ap)
{
delete
this
->_ptr;
this
->_ptr = ap._ptr;
// 转让权限
this
->_owner =
true
;
ap._owner =
false
;
}
return
*
this
;
}
//析构函数
~AutoPtr()
{
// 只删除拥有权限的指针
if
(_owner)
{
this
->_owner =
false
;
delete
this
->_ptr;
}
}
T& operator*()
{
return
*(
this
->_ptr);
}
T* operator->()
{
return
this
->_ptr;
}
T* AutoPtr<T>::GetStr()
{
return
(
this
->_ptr);
}
protected
:
T* _ptr;
bool
_owner;
//权限拥有者
};
|
出现的主要问题:如果拷贝出来的对象比原来的对象先出作用域或先调用析构函数,则原来的对象的_owner虽然为false,但却在访问一块已经释放的空间,原因在于拷贝对象的释放会导致原对象的_ptr指向的内容跟着被释放!
问题体现如下:
ap1将析构的权限给予了ap2,由于ap1._ptr和ap2._ptr指向同一份空间,ap2在出了if作用域之后自动被释放,进而导致ap1._ptr也被释放。
但是在if作用域之外,又对ap1._ptr进行访问,导致程序崩溃,这时候 ap1._ptr已经是一个野指针了,这就造成指针的悬挂的问题!
1
2
3
4
5
6
7
8
9
10
11
|
void
Test()
{
AutoPtr<
int
> ap1(
new
int
(1));
if
(
true
)
{
AutoPtr<
int
> ap2(ap1);
}
//这里的ap1._ptr已经是一个野指针了,这就造成指针的悬挂的问题
*(ap1.GetStr() )= 10;
}
|
auto_ptr的第二种实现方法:还是管理空间的所有权转移,但这种实现方法中没有_owner权限拥有者。构造和析构和上述实现方法类似,但拷贝和赋值后直接将_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
40
41
42
43
44
45
46
47
48
49
50
51
52
|
template
<
typename
T>
class
AutoPtr
{
public
:
//构造函数
explicit
AutoPtr(T* ptr = NULL)
//不能写成const T* ptr,否则出现const类型赋值给非const类型的问题
:_ptr(ptr)
{}
//拷贝构造
AutoPtr(AutoPtr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr=NULL;
}
//赋值运算符重载
AutoPtr& operator=(AutoPtr<T>& ap)
{
if
(
this
! = &ap)
{
delete
this
->_ptr;
this
->_ptr = ap._ptr;
ap._ptr = NULL;
}
return
*
this
;
}
//析构函数
~AutoPtr()
{
// 只删除拥有权限的指针
if
(_ptr)
{
delete
_ptr;
}
}
T& operator*()
{
return
*_ptr;
}
T* operator->()
{
return
_ptr;
}
T* GetStr()
{
return
_ptr;
}
protected
:
T* _ptr;
};
|
这种实现方式很好的解决了旧版本野指针问题,但是由于它实现了完全的权限转移,所以导致在拷贝构造和赋值之后只有一个指针可以使用,而其他指针都置为NULL,使用很不方便,而且还很容易对NULL指针进行解引用,导致程序崩溃,其危害也是比较大的。
scoped_ptr 独占所有权,防拷贝
scoped_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
|
template
<
typename
T>
class
ScopedPtr
{
public
:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{}
T& operator*()
{
return
*_ptr;
}
T* operator->()
{
return
_ptr;
}
T* GetStr()
{
return
_ptr;
}
//析构函数
~ScopedPtr()
{
if
(_ptr!=NULL)
{
delete
_ptr;
}
}
protected
:
//防拷贝
ScopedPtr(ScopedPtr<T>& ap);
ScopedPtr& operator=(ScopedPtr<T>& ap);
T* _ptr;
};
|
scoped_ptr的实现和auto_ptr非常类似,不同的是 scoped_ptr有着更严格的使用限制——不能拷贝,这就意味着scoped_ptr 是不能转换其所有权的。
在一般的情况下,如果不需要对于指针的内容进行拷贝,赋值操作,而只是为了防止内存泄漏的发生,scoped_ptr完全可以胜任。