分享
 
 
 

翻译:Effective C++, 3rd Edition, Item 31: 最小化文件之间的编译依赖(上)

王朝c/c++·作者佚名  2006-02-01
窄屏简体版  字體: |||超大  

Item 31: 最小化文件之间的编译依赖

作者:Scott Meyers 译者:fatalerror99 (iTePub's Nirvana)

你进入到你的程序中,并对一个类的实现进行了细微的改变。提醒你一下,不是类的接口,只是实现,仅仅是 private 的东西。然后你重建(rebuild)这个程序,预计这个任务应该只花费几秒钟。毕竟只有一个类被改变。你在 Build 上点击或者键入 make(或者其它等价行为),接着你被惊呆了,继而被郁闷,就像你突然意识到整个世界都被重新编译和连接!当这样的事情发生的时候,你不讨厌它吗?

问题在于 C++ 没有做好从实现中剥离接口的工作。一个类定义不仅指定了一个类的接口而且有相当数量的实现细节。例如:

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; // implementation detail

Date theBirthDate; // implementation detail

Address theAddress; // implementation detail

};

在这里,如果不访问 Person 的实现使用到的类,也就是 string,Date 和 Address 的定义,类 Person 就无法编译。这样的定义一般通过 #include 指令提供,所以在定义 Person 类的文件中,你很可能会找到类似这样的东西:

#include <string>

#include "date.h"

#include "address.h"

不幸的是,这样就建立了定义 Person 的文件和这些头文件之间的编译依赖关系。如果这些头文件中的一些发生了变化,或者这些头文件所依赖的文件发生了变化,包含 Person 类的文件和使用了 Person 的文件一样必须重新编译,这样的层叠编译依赖关系为项目带来数不清的麻烦。

你也许想知道 C++ 为什么坚持要将一个类的实现细节放在类定义中。例如,你为什么不能这样定义 Person,单独指定这个类的实现细节呢?

namespace std {

class string; // forward declaration (an incorrect

} // one - see below)

class Date; // forward declaration

class Address; // forward declaration

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;

...

};

如果这样可行,只有在类的接口发生变化时,Person 的客户才必须重新编译。

这个主意有两个问题。第一个,string 不是一个类,它是一个 typedef (for basic_string<char>)。造成的结果就是,string 的前向声明(forward declaration)是不正确的。正确的前向声明要复杂得多,因为它包括另外的模板。然而,这还不是要紧的,因为你不应该试着手动声明标准库的部件。作为替代,直接使用适当的 #includes 并让它去做。标准头文件不太可能成为编译的瓶颈,特别是在你的构建环境允许你利用预编译头文件时。如果解析标准头文件真的成为一个问题。你也许需要改变你的接口设计,避免使用导致不受欢迎的 #includes 的标准库部件。

第二个(而且更重要的)难点是前向声明的每一件东西必须让编译器在编译期间知道它的对象的大小。考虑:

int main()

{

int x; // define an int

Person p( params ); // define a Person

...

}

当编译器看到 x 的定义,它们知道它们必须为保存一个 int 分配足够的空间(一般是在栈上)。这没什么问题,每一个编译器都知道一个 int 有多大。当编译器看到 p 的定义,它们知道它们必须为一个 Person 分配足够的空间,但是它们怎么推测出一个 Person 对象有多大呢?它们得到这个信息的唯一方法是参考这个类的定义,但是如果一个省略了实现细节的类定义是合法的,编译器怎么知道要分配多大的空间呢?

这个问题在诸如 Smalltalk 和 Java 这样的语言中就不会发生,因为,在这些语言中,当一个类被定义,编译器仅仅为一个指向一个对象的指针分配足够的空间。也就是说,它们处理上面的代码就像这些代码是这样写的:

int main()

{

int x; // define an int

Person *p; // define a pointer to a Person

...

}

当然,这是合法的 C++,所以你也可以自己来玩这种“将类的实现隐藏在一个指针后面”的游戏。对 Person 做这件事的一种方法就是将它分开到两个类中,一个仅仅提供一个接口,另一个实现这个接口。如果那个实现类名为 PersonImpl,Person 就可以如此定义:

#include <string> // standard library components

// shouldn't be forward-declared

#include <memory> // for tr1::shared_ptr; see below

class PersonImpl; // forward decl of Person impl. class

class Date; // forward decls of classes used in

class Address; // Person interface

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: // ptr to implementation;

std::tr1::shared_ptr<PersonImpl> pImpl; // see Item 13 for info on

}; // std::tr1::shared_ptr

这样,主类(Person)除了一个指向它的实现类(PersonImpl)的指针(这里是一个 tr1::shared_ptr ——参见 Item 13)之外不包含其它数据成员。这样一个设计经常被说成是使用了 pimpl 惯用法(指向实现的指针 "pointer to implementation")。在这样的类中,那个指针的名字经常是 pImpl,就像上面那个。

用这样的设计,使 Person 的客户脱离 dates,addresses 和 persons 的细节。这些类的实现可以随心所欲地改变,但 Person 的客户却不必重新编译。另外,因为他们看不到 Person 的实现细节,客户就不太可能写出以某种方式依赖那些细节的代码。这就是接口和实现的真正分离。

这个分离的关键就是用对声明的依赖替代对定义的依赖。这就是最小化编译依赖的精髓:只要能实现,就让你的头文件独立自足,如果不能,就依赖其它文件中的声明,而不是定义。其它每一件事都从这个简单的设计策略产生。所以:

(未完待续)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有