关于effective c++中用句柄类降低编译依赖型,百思不得其解
比如。
Person类用句柄类
Person.h
#ifndef PERSON_H
#define PERSON_H
class Mystring;
class PersonImpl;
class Person
{
public:
Person();
Mystring name() const;
private:
PersonImpl *impl;
};
#endif
------------------------------------
PersonImple.h
#ifndef PERSONIMPL_H
#define PERSONIMPL_H
class Mystring;
class PersonImpl
{
public:
Mystring name() const;
private:
Mystring name_;
};
#endif
------------------------------------
PersonImple.cpp
#include "String.h"
#include "PersonImpl.h"
Mystring PersonImpl::name() const
{
return name_;
}
------------------------------------
String.h
#ifndef STRING_H
#define STRING_H
class Mystring
{
public:
Mystring();
};
#endif
这是不是就是作者所说的意思呢。可是我如果使用Person类还是会报出编译错误,说没有定义Mystring类啊。
因为Person类的实现cpp依然要include PersonImpl.h,依然需要知道Mystring类啊。
问题点数:100、回复次数:19Top
1 楼qhfu(改个名字)回复于 2006-02-26 23:12:03 得分 0
指针的话,只要加一个前置声明就可以了, 只需要知道类的声明不需要类的定义,所以就不需要#include *.h 文件了。类可以声明多次,但是只能定义一次
Mystring name() const;//这里是返回一个对象,因此需要#include *.hTop
2 楼ox_thedarkness()回复于 2006-02-26 23:21:45 得分 100
- - 很简单阿。为了更明确目的,我们把PersonImpl* 换成void*
你看下面的头文件:
class MyClass{
void* pImp;
public:
MyClass();
func();
};
头文件仅有这些外部接口。 很明显,MyClass 的创建和 func调用可以完成,因为MyClass的大小为4字节, 函数调用则只需要外部函数调用占位符。 连接的时候,他会寻找 obj文件并且产生真正的调用地址。
又因为,它不包含实现,所以你无论如何修改实现,都只影响这个类对应的obj。
再看实现。我们有一个 void* 可以指向任何动态创建的东西,我们把它指向实现文件中自定义的类实体; 所有函数都可以自己定义... 你想怎么做都可以... 还缺什么呢?Top
3 楼ox_thedarkness()回复于 2006-02-26 23:53:45 得分 0
- -b
作者说的是,
假如不这么做,数据成员都是类的直接成员,比如下面的实现:
class Person{
public:
Person();
Mystring name() const;
private:
Mystring name_;
};
当你决定往里面增加一个成员 int id ,那么就需要修改 Person.h 。 这会引起所有引用(以及间接引用) Person.h 的 cpp 文件重新编译成 obj,然后连接。 可以想象,一个比较底层的类,搞不好会令所有 cpp 都重新连接。
而用作者的最终版本,Person的实现全部在 PersonImple 和 Person.cpp 中,所以修改其内部,比如增加一个int 之类时候,都不需要修改 Person.h,只需要编译 Person.cpp,然后连接即可。
只有修改其外部接口,即其成员方法的时候,才需要修改.h,引起全部编译。
===============================================================
而 String, 他是返回值好不好, 你的main当然要包含了。Top
4 楼xiayuxia(LP_ME)回复于 2006-02-27 07:55:45 得分 0
指针只要 声明就可以了的啊 ;
Top
5 楼xiayuxia(LP_ME)回复于 2006-02-27 08:03:54 得分 0
PersonImple.h
里面还是要有 String.h的啊
这一节我也看过 ,只是没有去 实践下 ;Top
6 楼Mephisto_76((望美人如梦))回复于 2006-02-27 09:03:58 得分 0
前向声明只有在不需要知道对象内存布局时才能有用。你这里的属性Mystring name_;定义使得编译器在编译时需要知道Mystring的定义,所以需要包含头文件,如果改用MyString* name_;
并且Mystring name() const;也改成Mystring* name() const;且头文件中不引用Mystring的任何方法和属性时就可以了。Top
7 楼ugg(逸学堂(exuetang.net))回复于 2006-02-27 09:30:44 得分 0
文件编译是,如果把包含文件放入
.h文件中,会增加编译负担(因为编译器从.h文件开始编译)。
所以需要把.h文件放入cpp文件中,这样可以加速编译时间,并且降低编译
依赖型。
~~~~~~~~~~~
当在头文件中.h需要使用一个类时,
可以
class AA;// 声明一个类
但是必须在
.cpp文件中包括这个类的定义,即声明文件。
这样才可以达到上面的目的
Top
8 楼khalidwind(追风浪子)回复于 2006-02-27 09:59:02 得分 0
感谢大家的回复。
首先,我将作者的思路整理一下,大家看对不对。
要解决的问题是:在Person.h中不用include那么多辅助类(比如Mystring)的头文件,而采用前向声明的办法。这样当Mystring的头文件改动的时候,不需要编译Person.cpp。
但这样出现问题,就是在定义一个实例的时候,无法确定该实例的大小。所以在private中用一个PersonImpl指针代替具体的实现细节。
作者是这个意思吧?如果是这样的话那ox_thedarkness() 的解释就不对了。Person变成句柄类当然可以使得所有inclue Person.h的CPP不需要重新编译。但是任何Mystring.h的变动仍然会引起Person.cpp的重新编译啊。这并没有解决问题啊。更何况该如何实现PersonImpl类呢。
事实上,如果PersonImple.h不去include Mystring.h的话还是不能通过编译的。
而PersonImple.h 引用了Mystring.h。 Person.cpp必然要include PersonImpl.h,这不还是一样吗,还是相当于Person.cpp include了 Mystring.h啊。
MD,一会中文一会英文,累!
其实我倒觉得Mephisto_76((望美人如梦)) 的说法是对的,可是太麻烦了。也不是作者的代码。难道不能迷信大师?
另外,关于返回一个对象的时候是否需要知道这个类的实现,请看此节中的一句话
--------------
因为在声明一个函数时,如果用到某个类,是绝对不需要这个类的定义的,即使函数是通过传值来传递和返回这个类:
class Date; // 类的声明
Date returnADate(); // 正确 ---- 不需要Date的定义
---------------
这是作者的原话。
Top
9 楼xiayuxia(LP_ME)回复于 2006-02-27 11:10:58 得分 0
作者的原话我 看 过
马上 实践下 看看Top
10 楼YufengShi(浪子)回复于 2006-02-27 11:40:23 得分 0
Person.cpp在源代码一级依赖于Mystring
则Mystring有任何变化,Person.cpp一定要重新编译的.
除非用COM,在二进制一级使用Mystring.Top
11 楼ox_thedarkness()回复于 2006-02-27 12:24:28 得分 0
- __ -b
我手头的是2nd, 侯捷中译版,华工出版社。
1 作者只是演示一种方法,降低文件依赖性。降低,不是去掉。你要是头文件都改了,其他文件可能不需要重新编译么? 所以他建议1 头文件尽量不要放实现。 2 头文件尽量不 include 头文件。
2 他演示了,降低 Person 的用户与其实现的编译依赖,以及降低Person 所使用的类与其依赖性。
3 作者用的 std::string, 当然你可以用你的 MyString。
4 作者说,如果不依赖内部实现,一个前部声明就可以连接了。但是最终文件必须包含所有头文件。
下面是 P145 上部的原文摘录:
class string; // 针对 string 型别的“观念性”前置声明
// 条款 49 会有更清楚地说明
// [摘注:即,这样声明是错误的,string不是class 而是模板,49正确声明解释]
class Date;
class Address;
class Country;
class Person{
public:
Person( const string& name, const Data& birthday,
const Address& addr, const Country& countery );
//....
}
但是,如何使用呢? 作者在P147和 P148做了解释:
“如果你奇怪为什么...不需要 Data的定义,我告诉你,其实没那么神奇。当你调用那些函数时,Data的定义必须可见才行。...”
“不要再在文件中再#include 其他头文件,除非你的头文件不这样就无法编译。你应该尽可能手动声明你需要的classes,把 #include 其他头文件(从而使整个程序能够编译)的责任让给 clients.... ”
- - 明白了么?
顺便说一句,如果你真地认为找到了错误,前言中作者这么说:
“因此,如果有人挑出本书的任何错误并告诉我 —— 不论是技术、文法、错别字或其他任何东西 —— 我将在本书重新印刷的时候,把第一位挑出错误的读者大名加到致谢名单中。
...
...或者传送电子邮件到 ec++@awl.com。
”
什么叫大师? 海纳百川,有容乃大。Top
12 楼khalidwind(追风浪子)回复于 2006-02-27 14:34:14 得分 0
谢谢楼上。但我还是不大明白:(
也许作者只是演义一个方法,但起码这个方法要弄通吧。
我就是不理解,Person.cpp肯定需要PersonImpl的实现,所以Person.cpp必须包含PerseonImpl.h。但是PersonImpl又必须包含Mystring的实现细节。Person.cpp如果不包含Mystring.h的话,如何能通过编译呢?
作者想达到降低编译依赖性的目的,那这个被降低的依赖性到底是哪个文件和哪个文件的依赖型呢?Top
13 楼ericqxg007(还有很多东西要学(卡卡一米阳光))回复于 2006-02-27 14:56:27 得分 0
mark 我也不大懂Top
14 楼ox_thedarkness()回复于 2006-02-27 17:25:27 得分 0
降低的是 Person.h 和其他文件的依赖性。
想象你们是一个20人开发的项目, 你负责数据查询模块, 你左边这位负责数据结构模块,他管 Person 的实现,即:他提供两个文件:
Person.h (包括所有对外接口,即 protected、 public 方法等。但是数据方面只包含一个指针。接口是你们开会讨论的结果,不允许修改)
Person. obj
你根本不知道( 当然,既然他坐在你旁边,你也许听说了;说不定他还找你咨询技术问题呢? ),也不需要知道 PersonImpl 。 你只需要知道, Person::name 返回一个 MyString 表示他的名字。 而且你知道Person.h没有 include MyString。 你需要自己 #include <MyString.h>。
现在,只要 Person 的接口不变 —— 除非下次开会,否则有什么会变呢? 他只需要提交新的 Person. obj 即可。 他怎么修改 PersonImpl 和你有什么关系呢?
- - 现在假如,你左边那位都不这么做, 把所有数据成员写在对外的头文件里面。 好嘛。 他决定把给内部的数据改为一个智能指针。 你不知道这么回事,但是当你编译 exe的时候, 20个人(他们都用到了 Person.h )的 cpp 都开始重新编译生成 obj 拉....
Top
15 楼ox_thedarkness()回复于 2006-02-27 17:37:59 得分 0
继续看看这个模式,你不是说 MyString 的实现么?
假如你后面那位短发效率狂、数据结构狂负责维护 MyString (以及一大堆其他底层数据,比如你们自己的Vector ),他同样使用这种技术: 他提供
MyString.h 和 .obj
Vector.h 和 .obj
你维护自己的 Search.h 和 Search.obj
你们的头文件中都不包含其他 .h 的引用。
现在,他如何修改 MyString 的实现和你同样没关系,只要接口无需修改 —— 你的 obj 只需要他们的.h 就可以生成。Top
16 楼pongba(刘未鹏|http://blog.csdn.net/pongba)回复于 2006-02-27 19:48:48 得分 0
弄清楚接口依赖跟实现依赖之间的关系。pImpl模式是为了解开实现依赖而不是接口依赖。
ox_thedarkness()说的是对的。Top
17 楼Jiana(Robin.English)回复于 2006-02-27 23:07:10 得分 0
markTop
18 楼khalidwind(追风浪子)回复于 2006-02-28 10:11:16 得分 0
感谢ox_thedarkness()。
我想我懂你的意思了。
但还有最后一个实现上的问题。
你也说了Person.h没有 include MyString,实际上它也不能include MyString,它只是将MyString前置声明了。那请问当它需要MyString的实现的时候该怎么办。
PersonImpl该如何实现呢。
我现在就是无法编译Person.cpp,换句话说,我无法提供obj给clientTop
19 楼khalidwind(追风浪子)回复于 2006-02-28 10:16:35 得分 0
搞定了。谢谢Top




