设为首页 加入收藏

TOP

读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低(二)
2017-10-13 10:06:40 】 浏览:10143
Tags:读书 笔记 effective Item 文件 之间 编译 依赖 降到 最低
mplementation;
31 32 33 std::tr1::shared_ptr<PersonImpl> pImpl; // see Item 13 for info on 34 }; // std::tr1::shared_ptr

 

这里,主类(Person)没有包含任何数据成员,只包含了指向类实现的指针(PersonImpl),一个tr1::shared_ptr指针(Item 13)。这样一个设计就是通常所说的“pimpl idiom”(指向实现的指针)。在这样的类中,指针的名字通常为pImpl,如上面所示。

使用这个设计,Person的用户脱离了datas,address和persons的实现细节。这些类的实现可以随意修改,但Person用户不需要重新编译。此外,因为他们不能够看到Person的实现细节,用户应该不会写出依赖这些细节的代码。这就对实现和接口进行了真正的分离。

4. 最小化编译依赖的设计策略

分离的关键在于把对定义的依赖替换为对声明的依赖。这是最小化编译依赖的本质:在切实可行的情况下让你的头文件能够自给自足,如果达不到这个要求,依赖其他文件中的声明而不是定义。其他的设计都来自于这个简单的设计策略。因此:

 

  • 当使用指向对象的引用和指针能够做到时就不要使用对象。你可以只用一个声明来定义指向一个类型的引用和指针。而定义一个类型的对象则需要使用类的定义。
  • 尽可能用类的声明替换类的定义。注意使用类来声明一个函数的时候你绝不会用到这个类的定义,甚至使用按值传递参数或者按值返回也不需要:

 

1 class Date; // class declaration
2 
3 Date today();     // fine — no definition
4 
5    id clearAppointments(Date d); // of Date is needed

 

 

当然,按值传递通常情况下是一个坏方法(Item 20),但是如果你发现自己因为某种原因需要使用它,引入不必要的编译依赖也是没有任何理由的。

 

声明today和clearAppointments时不需要对Date进行定义可能会让你感到吃惊,但是它不像看上去那样让人好奇。如果任何人调用这些函数,Data的定义必须在函数调用之前被看到。你可能纳闷为什么声明无人调用的函数呢?很简单。不是没有人会调用它们,而是不是所有人都会调用它们。如果你有一个库包含很多函数声明,每个用户都调用每个函数是不太可能的。通过把在声明函数的头文件中提供类定义的责任转移到包含函数调用的客户文件中,你就消除了不必要的人为造成的对类型定义的用户依赖。

 

  • 为声明和定义分别提供头文件

 

为了符合上述方针,头文件需要成对使用:一个用于声明,一个用于定义。当然这些文件应该保持一致。如果一个地方的声明被修改了,两个地方必须同时修改。最后,库的用户应该总是#include一个声明文件,而不是自己对其进行前向声明,。举个例子,Date类的客户想声明today和clearAppointments,这里就不用像上面那样对Date进行前向声明了。而是应该#include包含了声明的头文件:

1 #include "datefwd.h" // header file declaring (but not
2 
3 // defining) class Date
4 
5 Date today(); // as before
6 
7 void clearAppointments(Date d);

 

头文件“datefwd.h”只包含声明,它的命名是基于标准C++库的头<iosfwd>(Item 54)。<iosfwd>包含了iostream组件的声明,与这些声明相对应的定义被放在几个不同的头中,包括<sstream>,<streambuf>,<fstream>和<iostream>。

<iosfwd>有另外一个指导性的意义,就是要弄清楚这个条款的建议不仅适用于templates,同样适用于非templates。虽然Item30解释了在许多编译环境中·,模板定义通常会放在头文件中,一些编译环境也允许将模板定义放在非头文件中,因此为模板提供只包含声明的头仍然是有意义的。<iosfwd>就是这样的头。

 

C++中同样提供了export关键字,它可以使模板声明从模板定义中分离出来。不行的是,支持export的编译器是稀少的,在现实世界中使用export的经验同样稀少。因此,评价

export会在高效C++编程中发挥什么作用还为时尚早。,

 

5. 句柄类

像Person这样使用了“指向实现的指针”(pimpl idiom)的类通常被叫做句柄类(handle class),如果你想知道这样的类是如何做到无所不能的,一种方法是将所有的函数调用转移到对应的实现类中,真正的工作在实现类中进行。举个例子,下面展示了Person类的两个成员函数是如何被实现的:

 1 #include "Person.h" // we’re implementing the Person class,
 2 
 3 // so we must #include its class definition
 4 
 5 #include "PersonImpl.h" // we must also #include PersonImpl’s class
 6 
 7 // definition, otherwise we couldn’t call
 8 
 9 // its member functions; note that
10 
11 // PersonImpl has exactly the same public
12 
13 // member functions as Person — their
14 
15 // interfaces are identical
16 
17 Person::Person(const std::string& name, const Date& birthday,
18 
19 const Address& addr)
20 
21 : pImpl(new PersonImpl(name, birthday, addr))
22 
23 {}
24 
25 std::string Person::name() const
26 
27 {
28 
29 return pImpl->name();
30 
31 }

 

 

注意Person构造函数是如何调用PersonImpl构造函数的(通过使用new Item 16),以及Person::name是如何调用PersonImpl::name的,这很重要。将Person类定义成句柄类并没有改变Person类能做什么,只是修改了Person类做什么的实现方式。

6. 抽象基类

使用句柄类的另外一种替代方法是将Person定义成特殊的抽象基类,也就是接口类。使用这种类的意图是为派生类指定一个

首页 上一页 1 2 3 4 下一页 尾页 2/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇运算符重载 与 sort() 下一篇bzoj4771 -- dfs序+倍增+主席树

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目