C++ 教程 | C11 智能指针
由于 C++ 语言没有自动内存回收机制,程序员每次new
出来的内存都要手动delete
。程序员忘记delete
,流程太复杂,最终导致没有delete
,异常导致程序过早退出,没有执行delete
的情况并不罕见。
对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了operator->
操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用.
操作符。
访问智能指针包含的裸指针则可以用 get()
函数。由于智能指针是一个对象,所以if (my_smart_object)
永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if (my_smart_object.get())
。
智能指针包含了 reset()
方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。
auto_ptr
std::auto_ptr
属于 STL,当然在 namespace std
中,包含头文件#include<memory>
便可以使用。 std::auto_ptr
能够方便的管理单个堆内存对象。
我们从代码开始分析:
1
2
3
4
5
6
7
8
9
{
std::auto_ptr<A> a_ptr(new A()); // 创建对象
if(a_ptr.get()) // 判断智能指针是否为空
{
a_ptr->show_message(); // 使用 operator-> 调用智能指针对象中的函数
a_ptr.get()->show_message(); // 使用 get() 返回裸指针
}
} // a_ptr 生命周期结束,析构堆对象 A
上述为正常使用 std::auto_ptr 的代码,一切似乎都良好,无论如何不用我们显示使用该死的delete 了。
问题 1
1
2
3
4
5
6
7
8
9
10
void TestAutoPtr2() {
std::auto_ptr<A> my_memory(new A(1));
if (my_memory.get()) {
std::auto_ptr<A> my_memory2; // 创建一个新的 my_memory2 对象
my_memory2 = my_memory; // 复制旧的 my_memory 给 my_memory2
my_memory2->show_message(); // 输出信息,复制成功
my_memory->show_message(); // 崩溃
}
}
最终如上代码导致崩溃,如上代码时绝对符合 C++ 编程思想的,居然崩溃了,跟进std::auto_ptr
的源码后,我们看到,罪魁祸首是my_memory2 = my_memory
,这行代码,my_memory2
完全夺取了 my_memory
的内存管理所有权,导致 my_memory
悬空,最后使用时导致崩溃。
所以,使用 std::auto_ptr 时,绝对不能使用“operator=”操作符。作为一个库,不允许用户使用,确没有明确拒绝,多少会觉得有点出乎预料。
问题 2
1
2
3
4
5
6
7
void TestAutoPtr3() {
std::auto_ptr<A> my_memory(new A(1));
if (my_memory.get()) {
my_memory.release();
}
} // 没有析构
看到什么异常了吗?我们创建出来的对象没有被析构,导致内存泄露。当我们不想让my_memory
继续生存下去,我们调用 release()
函数释放内存,结果却导致内存泄露(在内存受限系统中,如果my_memory
占用太多内存,我们会考虑在使用完成后,立刻归还,而不是等到my_memory
结束生命期后才归还)。
1
2
3
4
5
6
7
void TestAutoPtr3() {
std::auto_ptr<A> my_memory(new A(1));
if (my_memory.get()) {
A* temp_memory = my_memory.release();
delete temp_memory;
}
}
或
1
2
3
4
5
6
void TestAutoPtr3() {
std::auto_ptr<A> my_memory(new A(1));
if (my_memory.get()) {
my_memory.reset(); // 释放 my_memory 内部管理的内存
}
}
unique_ptr
unique_ptr
由 C++11
引入,旨在替代不安全的 auto_ptr
。unique_ptr
是一种定义在头文件<memory>
中的智能指针。
它持有对对象的独有权——两个unique_ptr
不能指向一个对象,即 unique_ptr
不共享它所管理的对象。
它无法复制到其他unique_ptr
,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。只能移动 unique_ptr
,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个unique_ptr
,并且原始 unique_ptr
不再拥有此资源。
实际使用中,建议将对象限制为由一个所有者所有,因为多个所有权会使程序逻辑变得复杂。因此,当需要智能指针用于存 C++ 对象时,可使用 unique_ptr
,构造 unique_ptr
时,可使用 make_unique
Helper 函数。
下图演示了两个 unique_ptr 实例之间的所有权转换。
unique_ptr
与原始指针一样有效,并可用于 STL 容器。将 unique_ptr
实例添加到 STL 容器运行效率很高,因为通过 unique_ptr
的移动构造函数,不再需要进行复制操作。
unique_ptr
指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过 reset
方法重新指定、通过 release
方法释放所有权、通过移动语义转移所有权,unique_ptr
还可能没有对象,这种情况被称为 empty
。
1
2
3
4
5
6
7
8
9
10
11
12
//智能指针的创建
unique_ptr<int> u_i; //创建空智能指针
u_i.reset(new int(3)); //绑定动态对象
unique_ptr<int> u_i2(new int(4));//创建时指定动态对象
unique_ptr<T,D> u(d); //创建空 unique_ptr,执行类型为 T 的对象,用类型为 D 的对象 d 来替代默认的删除器 delete
//所有权的变化
int *p_i = u_i2.release(); //释放所有权
unique_ptr<string> u_s(new string("abc"));
unique_ptr<string> u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针”
u_s2.reset(u_s.release()); //所有权转移
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价
shared_ptr
shared_ptr
是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在memory
文件中,命名空间为std
。 shared_ptr
最初实现于Boost库中,后由 C++11
引入到 C++ STL
。
shared_ptr
利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr
共同管理同一个对象。 像 shared_ptr
这种智能指针,《Effective C++》称之为“引用计数型智能指针”(reference-counting smart pointer,RCSP)。
shared_ptr
是为了解决 auto_ptr
在对象所有权上的局限性(auto_ptr
是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:
shared_ptr
对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;- 时间上的开销主要在初始化和拷贝操作上,
*
和->
操作符重载的开销跟auto_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
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
int i;
A(int n):i(n) { };
~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
shared_ptr<A> sp2(sp1); //A(2)同时交由sp2托管
shared_ptr<A> sp3;
sp3 = sp2; //A(2)同时交由sp3托管
cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
A * p = sp3.get(); // get返回托管的指针,p 指向 A(2)
cout << p->i << endl; //输出 2
sp1.reset(new A(3)); // reset导致托管新的指针, 此时sp1托管A(3)
sp2.reset(new A(4)); // sp2托管A(4)
cout << sp1->i << endl; //输出 3
sp3.reset(new A(5)); // sp3托管A(5),A(2)无人托管,被delete
cout << "end" << endl;
return 0;
}
weak_ptr
weak_ptr
被设计为与 shared_ptr
共同工作,可以从一个 shared_ptr
或者另一个 weak_ptr
对象构造而来。
weak_ptr
是为了配合 shared_ptr
而引入的一种智能指针,它更像是 shared_ptr
的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 operator*
和 operator->
,因此取名为 weak,表明其是功能较弱的智能指针。
它的最大作用在于协助 shared_ptr
工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 weak_ptr
只对 shared_ptr
进行引用,而不改变其引用计数,当被观察的 shared_ptr
失效后,相应的 weak_ptr
也相应失效。
使用 weak_ptr
的成员函数 use_count()
可以观测资源的引用计数,另一个成员函数 expired()
的功能等价于 use_count()==0
,但更快,表示被观测的资源(也就是shared_ptr
管理的资源)已经不复存在。
weak_ptr
可以使用一个非常重要的成员函数lock()
从被观测的 shared_ptr
获得一个可用的 shared_ptr
管理的对象, 从而操作资源。但当 expired()==true
的时候,lock()
函数将返回一个存储空指针的 shared_ptr
。
weak_ptr的基本用法总结如下:
1
2
3
4
5
6
7
weak_ptr<T> w; // 创建空 weak_ptr,可以指向类型为 T 的对象。
weak_ptr<T> w(sp); // 与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型。
w=p; // p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象。
w.reset(); // 将 w 置空。
w.use_count(); // 返回与 w 共享对象的 shared_ptr 的数量。
w.expired(); // 若 w.use_count() 为 0,返回 true,否则返回 false。
w.lock(); // 如果 expired() 为 true,返回一个空 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
#include < assert.h>
#include <iostream>
#include <memory>
#include <string>
using namespace std;
int main()
{
shared_ptr<int> sp(new int(10));
assert(sp.use_count() == 1);
weak_ptr<int> wp(sp); //从shared_ptr创建weak_ptr
assert(wp.use_count() == 1);
if (!wp.expired()) //判断weak_ptr观察的对象是否失效
{
shared_ptr<int> sp2 = wp.lock();//获得一个shared_ptr
*sp2 = 100;
assert(wp.use_count() == 2);
}
assert(wp.use_count() == 1);
cout << "int:" << *sp << endl;
return 0;
}
如何选择智能指针
文简单地介绍了 C++ 标准模板库 STL 中四种智能指针,当然,除了STL 中的智能指针,C++ 准标准库 Boost 中的智能指针,比如boost::scoped_ptr、boost::shared_array、boost:: intrusive_ptr 也可以在实际编程实践中拿来使用.
- 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr
1
2
两个对象都包含指向第三个对象的指针
STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
- 如果程序不需要多个指向同一个对象的指针,则可使用
unique_ptr
。如果函数使用new
分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr
是不错的选择。这样,所有权转让给接受返回值的unique_ptr
,而该智能指针将负责调用 delete。可将unique_ptr
存储到 STL 容器中,只要不调用将一个unique_ptr
复制或赋值给另一个的算法(如 sort())。例如,可在程序中使用类似于下面的代码段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unique_ptr<int> make_int(int n)
{
return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int>& p1)
{
cout << *p1 << ' ';
}
int main()
{
...
vector<unique_ptr<int> > vp(size);
for(int i = 0; i < vp.size(); i++)
vp[i] = make_int(rand() % 1000); // copy temporary unique_ptr
vp.push_back(make_int(rand() % 1000)); // ok because arg is temporary
for_each(vp.begin(), vp.end(), show); // use for_each()
...
}
其中 push_back
调用没有问题,因为它返回一个临时 unique_ptr
,该 unique_ptr
被赋给 vp 中的一个 unique_ptr
。
另外,如果按值而不是按引用给 show()
传递对象,for_each()
将非法,因为这将导致使用一个来自 vp 的非临时 unique_ptr
初始化 pi,而这是不允许的。前面说过,编译器将发现错误使用 unique_ptr
的企图。
在 unique_ptr
为右值时,可将其赋给 shared_ptr
,这与将一个 unique_ptr
赋给另一个 unique_ptr
需要满足的条件相同,即 unique_ptr
必须是一个临时对象。与前面一样,在下面的代码中,make_int()
的返回类型为 unique_ptr<int>
1
2
3
unique_ptr<int> pup(make_int(rand() % 1000)); // ok
shared_ptr<int> spp(pup); // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000)); // ok
模板 shared_ptr
包含一个显式构造函数,可用于将右值 unique_ptr
转换为 shared_ptr
。shared_ptr
将接管原来归 unique_ptr
所有的对象。
在满足 unique_ptr
要求的条件时,也可使用 auto_ptr
,但 unique_ptr
是更好的选择。如果你的编译器没有 unique_ptr
,可考虑使用 Boost 库提供的 scoped_ptr
,它与 unique_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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
template <typename T>
class CustomPtr{
private:
struct CountManager {
int count;
std::string name;
CountManager(int count, const std::string name):count(count), name(name){}
CountManager(const CountManager &count_manager) =default;
CountManager& operator=(const CountManager &count_manager) = default;
};
CountManager *pcount; // 指针计数器
T * ptr; // 指针指向堆对象
void free_object();
public:
CustomPtr() = delete;
CustomPtr(T* pvalue, const std::string name); // 获取指针
CustomPtr(const CustomPtr<T>& object); // 拷贝构造
CustomPtr(CustomPtr<T>&& object) noexcept; // 移动构造
CustomPtr<T>& operator=(const CustomPtr<T>& object); // 指针拷贝
CustomPtr<T>& operator=(CustomPtr<T>&& object) noexcept; // 指针移动
const T * operator ->() const; // 访问指针内容
const T& operator*() const; // 访问指针对象
~CustomPtr();
};
template <typename T>
CustomPtr<T>::CustomPtr(T* pvalue, const std::string name)
:ptr(pvalue),
pcount(new CountManager(1, name)){
}
template <typename T>
void CustomPtr<T>::free_object() {
if(this->pcount == nullptr) return;
this->pcount->count --;
if(this->pcount->count == 0){
delete this->ptr;
this->ptr = nullptr;
delete this->pcount;
}
this->pcount = nullptr;
}
template <typename T>
CustomPtr<T>::CustomPtr(const CustomPtr<T> &object)
:ptr(object.ptr),
pcount(object.pcount){
object.pcount->count ++;
}
template <typename T>
CustomPtr<T>::CustomPtr(CustomPtr<T> &&object) noexcept
:pcount(object.pcount),
ptr(object.ptr){
object.ptr = nullptr;
object.pcount = nullptr;
}
template <typename T>
CustomPtr<T> &CustomPtr<T>::operator=(const CustomPtr<T> &object) {
if(this == &object){
return *this;
}
free_object();
this->ptr = object.ptr;
this->pcount = object.pcount;
this->pcount->count ++;
return *this;
}
template <typename T>
CustomPtr<T> & CustomPtr<T>::operator=(CustomPtr<T> &&object)noexcept {
if(this == &object){
return *this;
}
free_object();
this->ptr = object.ptr;
this->pcount = object.pcount;
object.ptr = nullptr;
object.pcount = nullptr;
return *this;
}
template <typename T>
const T * CustomPtr<T>::operator->() const {
return this->ptr;
}
template <typename T>
const T & CustomPtr<T>::operator*() const {
return *this->ptr;
}
template <typename T>
CustomPtr<T>::~CustomPtr() {
free_object();
}
转载内容: