《Effective C++》之条款31:将文件间的编译依存关系降至最低

简介:

《Effective C++》

条款31:将文件间的编译依存关系降至最低

       假设你对C++程序的某个class实现文件做了些轻微修改。注意,修改的不是class接口,而是实现,而且只改private成分。然后重新建置这个程序,预计只花数秒就好。毕竟只有一个class被修改。当你按下“Build”按钮或键入make指令时,会大吃一惊,然后感到困窘,因为你意识到整个世界都被重新编译个连接了!那么问题出在哪里呢???

问题出在C++并没有把“将接口从实现中分离”这事做的很好。Class的定义式不只详细描述了class接口,还包括十足的实现细目。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <string>
#include "date.h"
#include "address.h"
class  Person
{
public :
     Person( const  std::string& name, const  Date& birthday,
            const  Address& addr);
     std::string name()  const ;
     std::string birthDate()  const ;
     std::string address()  const ;
     ...
private :
     std::string theName; //实现细目
     Date theBirthDate; //实现细目
     Address theAddress; //实现细目
};

其中:

1
2
3
#include <string>
#include "date.h"
#include "address.h"

       由于这些头文件,使得Person定义文件和其含入文件之间形成了一种编译依存关系。如果这些头文件中有任何一个被改变,或这些头文件所依赖的其他头文件有任何改变,那么每一个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译。这样的连串编译依存关系会对许多项目造成难以形容的灾难。

如下是一个解决方案的思路:

将对象实现细目隐藏于一个指针背后。把Person分割成两个classes,一个只提供接口,另一个负责实现该接口。如果负责实现的那个所谓implementation class 取名为PersonImpl,Person将定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <string>
#include <memory>
 
class  PersonImpl; //Person实现类的前置申明
class  Date; //Person接口用到的classes(Date,Address)的前置申明
class  Address;
 
class  Person
{
public :
     Person( const  std::string& name, const  Date& birthday,
            const  Address& addr);
     std::string name()  const ;
     std::string birthDate()  const ;
     std::string address()  const ;
     ...
private :
     std::tr1::shared_ptr<PersonImpl> pImpl; //智能指针shared_ptr,指向实物
};

这个分离的关键在于以“申明的依存性”替换“定义的依存性”,那正是编译依存性最小化的本质:现实中让头文件尽可能自我满足,万一做不到,则让它与其他文件内的申明式相依。其他每一件事都源自于这个简单的设计策略:

1.如果使用object reference或object pointers可以完成任务,就不要使用objects。

2.如果能够,尽量以class申明式替换class定义式。

3.为申明式和定义式提供不同的头文件。


Handle classes其中具体做法1是将他们的所有函数转交给相应的实现类并由后者完成实际工作。例如下面是Person两个成员函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
#include "Person.h"
#include "PersonImpl.h"
 
Person::Person( const  std::string& name, const  Date& birthday,
                const  Address& addr)
                : pImpl( new  PersonImpl(name,birthday,addr))
{ }
 
std::string Person::name()  const
{
     return  pImpl->name();
}

请注意,Person构造函数以new调用PersonImpl构造函数,以及Person::name函数内调用PersonImpl::name。这是重要的,让Person变成一个Handle class并不会改变它做的事,只会改变它做事的方法。

另一个制作Handle class的办法是,令Person成为一种特殊的abstract base class,称为interface class。这种class的目的是详细一一描述derived classes的接口,因此它通常不带成员变量,也没有构造函数,只有一个virtual析构函数以及一组pure virtual函数,用来描述整个接口。


总结:

  1. 支持“编译依存性最小化”的一般构想是:相依于申明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。

  2. 程序库头文件应该以“完全且仅有申明式”的形式存在。这种做法不论是否设计template都适用。

2016-11-08 23:10:40

本文转自313119992 51CTO博客,原文链接:http://blog.51cto.com/qiaopeng688/1870849

相关文章
|
29天前
|
安全 编译器 C++
C++一分钟之-编译时计算:constexpr与模板元编程
【6月更文挑战第28天】在C++中,`constexpr`和模板元编程用于编译时计算,提升性能和类型安全。`constexpr`指示编译器在编译时计算函数或对象,而模板元编程通过模板生成类型依赖代码。常见问题包括误解constexpr函数限制和模板递归深度。解决策略包括理解规则、编写清晰代码、测试验证和适度使用。通过实战示例展示了如何使用`constexpr`计算阶乘和模板元编程计算平方。
41 13
|
29天前
|
存储 分布式数据库 API
技术好文:VisualC++查看文件被哪个进程占用
技术好文:VisualC++查看文件被哪个进程占用
|
1月前
|
编译器 C++
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
|
24天前
|
C++ 开发者
C++一分钟之-编译时计算:constexpr与模板元编程
【7月更文挑战第2天】C++的`constexpr`和模板元编程(TMP)实现了编译时计算,增强代码效率。`constexpr`用于声明编译时常量表达式,适用于数组大小等。模板元编程则利用模板进行复杂计算。常见问题包括编译时间过长、可读性差。避免方法包括限制TMP使用,保持代码清晰。结合两者可以解决复杂问题,但需明确各自适用场景。正确使用能提升代码性能,但需平衡复杂性和编译成本。
41 3
|
1月前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
23 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
1月前
|
C++ iOS开发 开发者
C++一分钟之-文件输入输出(I/O)操作
【6月更文挑战第24天】C++的文件I/O涉及`ifstream`, `ofstream`和`fstream`类,用于读写操作。常见问题包括未检查文件打开状态、忘记关闭文件、写入模式覆盖文件及字符编码不匹配。避免这些问题的方法有:检查`is_open()`、显式关闭文件或使用RAII、选择适当打开模式(如追加`ios::app`)以及处理字符编码。示例代码展示了读文件和追加写入文件的实践。理解这些要点能帮助编写更健壮的代码。
33 2
|
29天前
|
IDE 开发工具 C++
插件:CLion中使用C/C++ Single File Execution插件编译和运行单个文件
插件:CLion中使用C/C++ Single File Execution插件编译和运行单个文件
30 0
|
1月前
|
编译器 C++
《Effective C++ 改善程序与设计的55个具体做法》 第二章 构造/析构/赋值运算 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第二章 构造/析构/赋值运算 笔记
|
1月前
|
Linux C++
Linux C/C++目录和文件的更多操作
Linux C/C++目录和文件的更多操作
|
3天前
|
C++
什么是析构函数,它在C++类中起什么作用
什么是析构函数,它在C++类中起什么作用?
20 11