最近在看陈硕的《Linux多线程服务端编程:使用muduo C++网络库》,第一章中重点讲了多线程下指针的处理问题,其中重点是 shared_ptr 与 weak_ptr, 上两篇学习了 shared_ptr 的基本用法 ( shared_ptr类模版 ) 和 weak_ptr 的基本用法 ( waek_ptr类模版 ) ,今天继续学习 shared_ptr ,以下是对 Smart Pointer Programming Techniques 的部分中文翻译。
Using incomplete classes for implementation hiding
分离接口与实现的一个有效方式是使用不完整类型类的指针。1
2
3
4
5class FILE;
FILE * fopen(char const * name, char const * mode);
void fread(FILE * f, void * data, size_t size);
void fclose(FILE * f);
可以使用 shared_ptr 来表达上面的接口,使得不需要手动调用 fclose。
1 | class FILE; |
这种方式是利用了 shared_ptr 能够自动调用 deleter 的机制来去除了 fclose 的显示调用,实际上,当 X 是不完整类型时,shared_ptr< X > 能够被拷贝和销毁。
The “Pimpl” idiom
不完整类的一个 C++ 变种是 “Pimpl” idiom,不完整类没有暴露于用户,shared_ptr 能够用来实现一个 “Pimpl”:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// file.hpp:
class file
{
private:
class impl;
shared_ptr<impl> pimpl_;
public:
file(char const * name, char const * mode);
// compiler generated members are fine and useful
void read(void * data, size_t size);
};
1 | // file.cpp: |
这里的关键点是编译器产生的复制拷贝函数,赋值函数,析构函数都有其意义(代码中file没有定义),因此,file 是可以拷贝复制和赋值的,使得它能够用在标准容器里。
Using abstract classes for implementation hiding
另外一个在 C++ 中分离接口与实现的方式是使用 虚基类 和 工厂函数,虚类有时候被称为接口,这种模式被称为基于接口编程 (“interface-based programming” ),这时,shared_ptr 可以作为工厂函数的返回类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// X.hpp:
class X
{
public:
virtual void f() = 0;
virtual void g() = 0;
protected:
~X() {}
};
shared_ptr<X> createX();
留意 X_impl 放在了 cpp 中声明和定义。
1 | //X.cpp: |
shared_ptr 的一个关键属性是分配内存,构造函数,释放内存,析构函数的细节是在工厂函数的构造函数里面开始的。留意例子中的被保护的非虚析构函数(虚析构函数可以确保程序正确调用指针对象的析构函数,如果没有 virtual 关键字,则由指针的类型决定析构函数),客户代码不能够(也不需要)删除指向 X 的指针;createX 返回的 shared_ptr< X > 能够正确地调用 ~X_impl。
Preventing delete px.get()
阻止客户代码试图删除一个被 shared_ptr 管理的指针是经常需要的,前面的技术中展示了其中一个办法,使用 protected 的析构函数,另外一个方法是使用私有的 deleter:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class X
{
private:
~X();
class deleter;
friend class deleter;
class deleter
{
public:
void operator()(X * p) { delete p; }
};
public:
static shared_ptr<X> create()
{
shared_ptr<X> px(new X, X::deleter());
return px;
}
};
Using a shared_ptr to hold a pointer to an array
shared_ptr 能够用来保存 new[] 操作出来的数组的指针:1
shared_ptr<X> px(new X[1], checked_array_deleter<X>());
注意,如果有得选择,shared_array 更经常适合这个任务,它有一个专门为数组设计的接口,不需要 operator* 与 operator->,并且不需要指针的转化。(?)
Encapsulating allocation details, wrapping factory functions
shared_ptr 能够用来创建 C++ 的 wrappers 来适应现有 C 风格的库接口(库接口中工厂函数返回的指针),从而封装内存分配的细节。考虑下面这个接口,CreateX 可能从它的私有堆里分配内存给 X, ~X 可能无法访问,或者 X 可能不是完整的:1
2
3
4
5
6
7
8
9
10
11X * CreateX();
void DestroyX(X *);
```
销毁 CreateX 返回的指针的唯一方法是调用 DestroyX 函数。
shared_ptr 的 wrapper 可能如下:
```C++
shared_ptr<X> createX()
{
shared_ptr<X> px(CreateX(), DestroyX);
return px;
}
客户端代码调用 createX 不需要知道对象是如何被分配内存的,析构也是自动的。
Using a shared_ptr to hold a pointer to a statically allocated object
有时给一个已经存在的对象创建一个 shared_ptr ,因此,当没有更多引用剩下时,shared_ptr 不试图去销毁这个对象,如下工厂函数:1
shared_ptr<X> createX();
有些时候需要返回静态分配 X 的指针,解决方法是使用自定义的 deleter 不做任何东西:1
2
3
4
5
6
7
8
9
10
11
12
13
14struct null_deleter
{
void operator()(void const *) const
{
}
};
static X x;
shared_ptr<X> createX()
{
shared_ptr<X> px(&x, null_deleter());
return px;
}