shared_ptr类模版(2)

最近在看陈硕的《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
5
class 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
2
3
4
class FILE;

shared_ptr<FILE> fopen(char const * name, char const * mode);
void fread(shared_ptr<FILE> f, void * data, size_t size);

这种方式是利用了 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
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
// file.cpp:

#include "file.hpp"

class file::impl
{
private:

impl(impl const &);
impl & operator=(impl const &);

// private data

public:

impl(char const * name, char const * mode) { ... }
~impl() { ... }
void read(void * data, size_t size) { ... }
};

file::file(char const * name, char const * mode): pimpl_(new impl(name, mode))
{
}

void file::read(void * data, size_t size)
{
pimpl_->read(data, size);
}

这里的关键点是编译器产生的复制拷贝函数,赋值函数,析构函数都有其意义(代码中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
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
//X.cpp:

class X_impl: public X
{
private:

X_impl(X_impl const &);
X_impl & operator=(X_impl const &);

public:

virtual void f()
{

// ...
}

virtual void g()
{

// ...
}
};

shared_ptr<X> createX()
{
shared_ptr<X> px(new X_impl);
return px;
}

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
24
class 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
11
X * 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
14
struct null_deleter
{
void operator()(void const *) const
{

}
};

static X x;

shared_ptr<X> createX()
{
shared_ptr<X> px(&x, null_deleter());
return px;
}