C_and_CPP 版 (精华区)

发信人: seaboy (浪小), 信区: C_and_CPP
标  题: Virtually Yours
发信站: 哈工大紫丁香 (2003年06月17日15:42:28 星期二), 站内信件

Virtually Yours 
myheartforver 翻译

-------------------------------------------------------------------------------
-

  我又拧了一下扳手 ,但毫无用处,我应该试着用锤子猛敲它 ,但那看起来不是个好主
意。“它一动不动”,我说道,“剩余的空间足够再放一个模块,但原先那些零件固定得
很紧,动不了。” 

  机器只允许一个人操作,珍妮只能旁观,她忍不住抱怨:“,唉,真是的,往扳手上添
个零件就这么困难么?” 

  “如果零件没有正确组合在一起的话,就可以”,我叹道,“那些制造的人只需要考虑
东西能用就可以了,里面的东西都紧紧的合在一起了,行,我们可以添加零件,但是首先
,我们不得不把它先拆开来,然后再重装好。” 

“欢迎到木卫二, 自身体重牵引系统中心,回家后我们就拥有这项技术了,可以自己试试
了。” 

我摸了摸额头,“你知道”,我沉思一会说到,“这让我想起...”,珍妮笑了,这表明我
们可以休息一下喝杯咖啡了,我开始告诉她另一个关于我第一份工作的故事。 


-------------------------------------------------------------------------------
-

  我正在为一个设计方案编写代码,该方案是住我隔壁的程序员温迪设计的。她为项目设
计一个抽象类,而我的工作就是设计一个实体类继承她的抽象类。 

  就像往常一样,我通过公共接口来研究一个新的类: 

class Mountie
{
public:
    void read( std::istream & );
    void write( std::ostream & ) const;

    virtual ~Mountie();

};

  毫不惊奇—析构函数设计成虚拟函数,暗示这个类将作为基类使用;但用于读与写的成
员函数却是非虚拟的,这时我想要看看其他代码: 

  所以我把目光移到类的保护接口上: 

protected:
virtual void do_read( std::istream & );
virtual void do_write( std::ostream & ) const;

  很快,我意识到温迪使用了模板方法模式(Template Method pattern):公共、非虚拟
成员函数显然将调用受保护的虚拟函数[1]。我对很快理解了这点感到洋洋自得,我做好了
应付任何事情的准备。可当我看到私有接口时,我的这股得意劲消失了。 

private:
    virtual std::string classID() const = 0;

  我停止了自得,一个纯虚拟私有函数?它怎么可能工作呢?我百思不得其解。 

  “嗯,温迪”我说,“你的mountie类无法工作。它有一个私有的虚拟函数。” 

  “你试过吗?”温迪问道,头也没从她的键盘上抬一下。 

  “噢,好吧,没有”,我承认,“但我的继承类无法覆盖classID这个函数” 

  “你如此肯定?”Guru安静的声音把我们都吓了一跳。“和你在一起总是什么事都干不
成。难道这几个月来你什么都没学到,还是个菜鸟?你可是我的徒弟啊! 

  尽管Guru的语气很平静,但这几句咄咄逼人的话仍使我感到几分惶恐。 

  “我的孩子“,她继续说道,“你忘了存取权限和虚拟性是互相独立的,决定一个函数
是静态绑定还是动态绑定取决于这个函数最后是如何被调用的。你需要好好读读神圣标准
第三章4节第一行,和第五章2.2节第一行。 

  是时候展现我的水平了,我开口说:“噢,是的”,我决定使出另外一招---扯开话题
。“我只是不理解为什么它是私有的。这看起来好像对我没什么意义。” 

  “仔细考虑一下这种情况,徒弟。你创建了一个类,再仔细考虑一下它的设计以后,你
觉得这个类需要一个不能被任何其他类调用的成员函数,甚至是它的继承类,你会怎么做
呢?” 

 “当然是申明为私有了”,我回答道。Guru看着我,她的眉毛皱了起来。我的大脑开始
飞速的转了起来,拼命的想私有函数和虚拟函数之间有什么联系。 

  温迪说:“你检查过mountie类的实现没有,特别是write函数?” 

  我很快的转到我的键盘上,很高兴逃离Guru不松懈的注视。我很快就看到了它: 

void Mountie::write(std::ostream &Dudley) const
{
    Dudley << classID() << std::endl;

    do_write(Dudley);
}

 我应该告诉温迪我童年花了太多的时间看动画片了。“我现在明白了”,我说道“函数c
lassID是一个实现细节,用来表示正在保存的类的具体类型。继承类必须重载这个函数,
但既然它是个实现细节,那就让它为私有。” 

  “不错,小伙子”Guru点点头“现在解释给我听听为什么do_write和do_read不是私有
的。” 

  这让我想了好一会儿,突然灵机一动:“因为继承类必须调用它父类这些函数的实现以
使父类有机会读写继承类的数据。” 

  “很好,徒弟,”Guru几乎微笑了,“但为什么不让它们为公有?” 

  “因为,”我受到鼓舞继续说道,“它们必须以一种受控制的方式被调用,特别是do_wri
te函数,对象类型不得不首先写到流中,使得当对象被读取时,对象的创建者得以知晓对
象的类型以便从流中装载并创建对象。如果把这些函数设为公有,会导致混乱。 

  “非常聪明,徒弟。”Guru暂停了一会,“就像口语一样,c++语言除了语法和规则外
还有很多东西需要了解,你必须要知道这些东西。 

  “对,Coplien的书是我下一本要读的书。” 

Guru抬起手:“安静点,孩子。我不是说大师Coplien。我是说用词习惯的语言感觉---特
定结构下的隐含意思。举例来说,你知道,一个虚拟析构函数将会告诉你我将会作为多态
基类,你可以从我这里按你所需继承新类,但是一个非虚拟析构函数则告诉你我不会作为
多态基类,请不要从我继承,我祈祷。” 

    “类似的,虚拟函数的存取权限表达了初始化的通常意义。一个保护的虚拟成员告
诉你一些东西应该或可能甚至必须依赖于这个函数的实现。一个私有的虚拟函数表明某些
东西可能或者不可能会重载我,这取决于他们的选择。然而他们可能不会调用我的实现 。
” 

  当我领会到这点,我点点头 :“那么公共虚拟函数呢?” 

  “尽可能避免这样做,最好使用模板方法,考虑这种情况”,她拾起干擦笔,开始写了
起来,字迹很细: 

class HardToExtend
{
public:
    virtual void f();
};
void HardToExtend::f()
{
    // 执行专有的动作
}

  “假设你发布了这个类,但不久,你的需求发生了变化”,她继续说道,“在这个类的
第二个版本里,你发现在函数f()中需要实现模板方法(Template Method )。而这几乎不可
能实现,你知道这是为什么? 

  “啊,是的,好吧...我认为...不,我不知道。“ 

  “有两个可能的途径让这个类使用模板方法。第一个途径是把f()的实现代码移到一个
新的函数然后让f()成为非虚拟的,就像这样: 

class HardToExtend
{
    // 可以保护
    virtual void do_f();
public:
    void f();
};
void HardToExtend::f()
{
    // 前处理
    do_f();
    // 后处理
}
void HardToExtend::do_f()
{
    // 执行专有的动作
}

  “然而,HardToExtend’的继承类希望能覆盖f(),而不是do_f().你现在必须改变所有
从HardToExtend继承而来的类。只要你漏掉了一个,这个类将试图覆盖一个非虚拟函数,
这将会导致,用大师Meyers的话来说,在你的类继承体系中有不可预期的行为。 

  “另一个解决之道是引进非虚拟函数,然后把f()移到私有部分,像这样:” 

class HardToExtend
{
// possibly protected
virtual void f();
public:
void call_f();
};

  “这将会给类的使用者无休止的烦恼。所有HardToExtend的使用者都会试图调用f(),而
不是call_f()。 HardToExtend的客户代码根本就无法编译。此外,它的继承类将很可能把
f()放到公共接口里,然后使用这些继承类的客户代码会直接地,而不是通过HardToExtend
的指针和引用,对你想保护的函数进行存取。 

  “虚拟函数应该和数据成员一样对待----让他们成为私有的,除非设计需求表明应该有
较少的限制。提升它们到更高存取级别比把它们降到更私有的级别更容易些。” 


-------------------------------------------------------------------------------
-

  “这些事是什么时候发生的呢?”珍妮问道。 

  “就在新千年来临之前---真正的来临,也就是说,我想是在2000年末。我们那时候即
将庆贺新年,我们那时在展望2001年,因为所有的文化,你知道.. .” 

  很奇怪,这场谈话后没多久我们听说在木卫二地表下面发现了一个方尖塔。 


-------------------------------------------------------------------------------
-

[注释] 
[1] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements 
of Reusable Object-Oriented Software (Addison-Wesley, 1995). 

[1]E.Gamma,R,Helm,R.Johnson,and J.Vlissides.设计模式: 

[2] ISO/IEC 14882:1998, "Programming Languages — C++," clauses 3.4 and 
5.2.2.   

[3] J. Coplien. Advanced C++ Programming Styles and Idioms (Addison-Wesley, 
1992). 

[4] S. Meyers. Effective C++: 50 Specific Ways to Improve Your Programs and 
Design, 2nd edition (Addison-Wesley, 1998); Item 37: "Never redefine an 
inherited non-virtual function." 
 
--


欢迎到C_and_CPP讨论相关问题。

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.80]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:2.996毫秒