PIMPL(pointer to implementation)是一种常用的,用来对“类的接口与实现”进行解耦的方法。pimpl具有如下优点:
降低模块的耦合 降低编译依赖,提高编译速度 接口与实现分离 为了实现pimpl模式,我们先来看一种普通的类的设计方法。
假如我们要设计一书籍类Book,Book包含目录属性,并提供打印书籍信息的对外接口,Book设计如下:
1
2
3
4
5
6
7
8
|
<code
class
=
"hljs"
css=
""
>
class
Book
{
public
:
void
print();
private
:
std::string m_Contents;
};</code>
|
Book的使用者只需要知道print()接口,便可以使用Book类,看起来一切都很美好。
然而,当某一天,发现Book需要增加一标题属性,对Book类的修改如下:
1
2
3
4
5
6
7
8
9
|
<code
class
=
"hljs"
css=
""
>
class
Book
{
public
:
void
print();
private
:
std::string m_Contents;
std::string m_Title;
};</code>
|
虽然使用print()接口仍然可以直接输出书籍的信息,但是Book类的使用者却不得不重新编译所有包含Book类头文件的代码。
为了隐藏Book类的实现细节,实现接口与实现的真正分离,可以使用pimpl模式。
我们依然对Book类提供相同的接口,但Book类中不再包含原有的数据成员,其所有操作都由BookImpl类实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<code
class
=
"hljs"
cs=
""
>
/* public.h */
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED
class
Book
{
public
:
Book();
~Book();
void
print();
private
:
class
BookImpl;
// Book实现类的前置声明
BookImpl* pimpl;
};
#endif</code>
|
为简单实现起见,Book类省略了拷贝构造函数和拷贝赋值函数。在实际应用中,应该对这两个函数进行定义。
在对外的头文件public.h中,只包含Book类的外部接口,将真正的实现细节被封装到BookImpl类。为了不对外暴露BookImpl类,将其声明为Book类的内嵌类,并声明为private。
BookImpl类的头文件如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<code
class
=
"hljs"
css=
""
>
/* private.h */
#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED
#include
public
.h
#include <iostream>
class
Book::BookImpl
{
public
:
void
print();
private
:
std::string m_Contents;
std::string m_Title;
};
#endif</iostream></code>
|
private.h并不需要提供给Book类的使用者,因此,如果往后需要重新设计书籍类的属性,外界对此一无所知,从而保持接口的不变性,并减少了文件之间的编译依赖关系。
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
|
<code
class
=
"hljs"
cpp=
""
>
/* book.cpp */
#include
private
.h
// 我们需要调用BookImpl类的成员函数,
// 所以要包含BookImpl的定义头文件
#include
public
.h
// 我们正在实现Book类,所以要包含Book类
// 的头文件
Book::Book()
{
pimpl =
new
BookImpl();
}
Book::~Book()
{
delete pimpl;
}
void
Book::print()
{
pimpl->print();
}
/* BookImpl类的实现函数 */
void
Book::BookImpl::print()
{
std::cout << print from BookImpl << std::endl;
}</code>
|
使用Book类的接口的方法如下:
1
2
3
4
5
6
7
8
9
10
|
<code
class
=
"hljs"
vala=
""
>
/* main.cpp */
#include
public
.h
int
main()
{
Book book;
book.print();
return
0
;
}</code>
|
像Book类这样使用pimpl的类,往往被称为handle class,BookImpl类作为实现类,被称为implementation class。
使用pimpl带来的额外开销包括,handle class成员函数的每次调用都必须通过implementation class,这会增加一层间接性,另外,每一个对象都增加了一个implementation class指针的大小。在实际中你需要对这些开销进行权衡。