条款34: 将文件间的编译依赖性降至最低 这个条款不太理解

jieao111 2008-03-15 07:55:44
条款34: 将文件间的编译依赖性降至最低

假设某一天你打开自己的C++程序代码,然后对某个类的实现做了小小的改动。提醒你,改动的不是接口,而是类的实现,也就是说,只是细节部分。然后你准备重新生成程序,心想,编译和链接应该只会花几秒种。毕竟,只是改动了一个类嘛!于是你点击了一下"Rebuild",或输入make(或其它类似命令)。然而,等待你的是惊愕,接着是痛苦。因为你发现,整个世界都在被重新编译、重新链接!

当这一切发生时,你难道仅仅只是愤怒吗?

问题发生的原因在于,在将接口从实现分离这方面,C++做得不是很出色。尤其是,C++的类定义中不仅包含接口规范,还有不少实现细节。例如:

class Person {
public:
Person(const string& name, const Date& birthday,
const Address& addr, const Country& country);
virtual ~Person();

... // 简化起见,省略了拷贝构造
// 函数和赋值运算符函数
string name() const;
string birthDate() const;
string address() const;
string nationality() const;

private:
string name_; // 实现细节
Date birthDate_; // 实现细节
Address address_; // 实现细节
Country citizenship_; // 实现细节
};

这很难称得上是一个很高明的设计,虽然它展示了一种很有趣的命名方式:当私有数据和公有函数都想用某个名字来标识时,让前者带一个尾部下划线就可以区别了。这里要注意到的重要一点是,Person的实现用到了一些类,即string, Date,Address和Country;Person要想被编译,就得让编译器能够访问得到这些类的定义。这样的定义一般是通过#include指令来提供的,所以在定义Person类的文件头部,可以看到象下面这样的语句:

#include <string> // 用于string类型 (参见条款49)
#include "date.h"
#include "address.h"
#include "country.h"

遗憾的是,这样一来,定义Person的文件和这些头文件之间就建立了编译依赖关系。所以如果任一个辅助类(即string, Date,Address和Country)改变了它的实现,或任一个辅助类所依赖的类改变了实现,包含Person类的文件以及任何使用了Person类的文件就必须重新编译。对于Person类的用户来说,这实在是令人讨厌,因为这种情况用户绝对是束手无策。

那么,你一定会奇怪为什么C++一定要将一个类的实现细节放在类的定义中。例如,为什么不能象下面这样定义Person,使得类的实现细节与之分开呢?

class string; // "概念上" 提前声明string 类型
// 详见条款49

class Date; // 提前声明
class Address; // 提前声明
class Country; // 提前声明

class Person {
public:
Person(const string& name, const Date& birthday,
const Address& addr, const Country& country);
virtual ~Person();

... // 拷贝构造函数, operator=

string name() const;
string birthDate() const;
string address() const;
string nationality() const;
};

如果这种方法可行的话,那么除非类的接口改变,否则Person 的用户就不需要重新编译。大系统的开发过程中,在开始类的具体实现之前,接口往往基本趋于固定,所以这种接口和实现的分离将大大节省重新编译和链接所花的时间。


1:
谁能解释以下,为什么该了之后,就不需要重新编译?
还有
2;编译和链接做的事有什么不同啊?

...全文
116 10 打赏 收藏 举报
写回复
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
DayThinking 2012-07-08
  • 打赏
  • 举报
回复
你可以参考下这篇文章:http://blog.csdn.net/sszgg2006/article/details/7725999
dananhai 2008-03-17
  • 打赏
  • 举报
回复
再在仔细阅读下本条款。
Person类的接口暴露了实现细节:其类型为string, Date,Address和Country,这个接口需要包含下面的接口文件:

#include <string>
#include "date.h"
#include "address.h"
#include "country.h"

Person的改动可能的原因:
1)本身的数据表示和相关操作(成员函数)的细节。
2)由于string, Date,Address和Country的变动而引发的。
一旦发生变动,Person.cpp被编译是正常的,但这里由于暴露了实现细节,所有包含Person.h的文件都要跟着编译——浪费了编译时间。

还有一个不太让人注意的问题是:如果你Person类也作为通用类提供给其他模块使用,那么,那么使用Person类的这些模块只需要包含Person.h就可以了。不应该为Person类实现细节需要的其他库配置环境目录,因为这些使用者可能根本就没有这些库。(比如说红字部分的date.h是其他库)。
jieao111 2008-03-17
  • 打赏
  • 举报
回复
理解不了啊,,看来只能在看一遍书了...
michney 2008-03-16
  • 打赏
  • 举报
回复
mark
redjuly 2008-03-16
  • 打赏
  • 举报
回复
class Date; // 提前声明
只是一个声明,因为没有依赖定义Data的头文件,所以是不需要重编译的。
简单说就是告诉编译器,Data是有定义的,具体在什么地方等需要的时候再告诉你。
abupie 2008-03-15
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 collin1211 的回复:]
《Com本质论》也可以看看。从面向实现,到面向接口的编程,是C++程序员观念转变的一大步~
[/Quote]
[Quote=引用 4 楼 coding_hello 的回复:]
问题1:2楼说得很清楚了

问题2:编译是把每个含源代码的文件(*.c,cpp)翻译成独立的机器码文件(*.obj),链接是把各个obj文件合起来协同工作。
[/Quote]
正解
野男孩 2008-03-15
  • 打赏
  • 举报
回复
问题1:2楼说得很清楚了

问题2:编译是把每个含源代码的文件(*.c,cpp)翻译成独立的机器码文件(*.obj),链接是把各个obj文件合起来协同工作。
collin1211 2008-03-15
  • 打赏
  • 举报
回复
《Com本质论》也可以看看。从面向实现,到面向接口的编程,是C++程序员观念转变的一大步~
collin1211 2008-03-15
  • 打赏
  • 举报
回复
建议看一下《C++编程思想》.

1、不需重新编译不是指你的实现,而是别人使用你提供的代码时,不需要重编,因为接口没变,别人#include的是你的接口文件;
2、C++是独立的编译模型,函数调用、结构等在内存中的样子每个obj都是一样的,这样才能链起来,不会有问题。换言之,也就是说只要接口文件不变,别人调用你的接口函数的内存布局就不会变,你的接口函数实现修改是安全的。
moss830704 2008-03-15
  • 打赏
  • 举报
回复
mark
等待解答
相关推荐
发帖
C++ 语言

6.3w+

社区成员

C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
帖子事件
创建了帖子
2008-03-15 07:55
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下