Programming 版 (精华区)

发信人: zhangyan (我跟模电拼了……), 信区: Programming
标  题: C++不足之处的讨论(四)
发信站: 哈工大紫丁香 (2001年05月23日23:36:36 星期三), 站内信件

以下文章翻译自Ian Joyner所著的
《C++?? A Critique of C++ and Programming and Language Trends of the 
1990s》 3/E【Ian Joyner 1996】
原著版权属于Ian Joyner,征得Ian Joyner本人的同意,我得以将该文翻译成中文
。因此,本文的中文版权应该属于我;-)
该文章的英文及中文版本都用于非商业用途,你可以随意地复制和转贴它。不过最
好请在转贴时加上前面的这段声明。
如果有人或机构想要出版该文,请最好联系原著版权所有人及我。

另外,该篇文章已经包含在Ian Joyner所写的《Objects Unencapsulated 》一书
中(目前已经有了日文的翻译版本),该书的介绍可参见于:
http://www.prenhall.com/allbooks/ptr_0130142697.html
http://efsa.sourceforge.net/cgi-bin/view/Main/ObjectsUnencapsulated
http://www.accu.org/bookreviews/public/reviews/o/o002284.htm

Ian Joyner的联系方式:
 i.joyner@acm.org
我的联系方式:
 cber@email.com.cn

译者前言:
 要想彻底的掌握一种语言,不但需要知道它的长处有哪些,而且需要知道它的不
足之处又有哪些。这样我们才能用好这门语言,避免踏入语言中的一些陷阱,更好
地利用这门语言来为我们的工作所服务。
 Ian Joyner的这篇文章以及他所著的《Objects Unencapsulated 》一书中,向我
们充分的展示了C++的一些不足之处,我们应该充分借鉴于他已经完成的伟大工作
,更好的了解C++,从而写出更加安全的C++代码来。

C++的不足之处讨论系列(四)

函数重载

 C++允许在参数类型不同的前提下重载函数。重载的函数与具有多态性的函数(即
虚函数)不同处在于:调用正确的被重载函数实体是在编译期间就被决定了的;而
对于具有多态性的函数来说,是通过运行期间的动态绑定来调用我们想调用的那个
函数实体。多态性是通过重定义(或重写)这种方式达成的。请不要被重载
(overloading)和重写(overriding)所迷惑。重载是发生在两个或者是更多的函数
具有相同的名字的情况下。区分它们的办法是通过检测它们的参数个数或者类型来
实现的。重载与CLOS中的多重分发(multiple dispatching)不同,对于参数的多
重分发是在运行期间多态完成的。
 
 【Reade 89】中指出了重载与多态之间的不同。重载意味着在相同的上下文中使
用相同的名字代替出不同的函数实体(它们之间具有完全不同的定义和参数类型)
。多态则只具有一个定义体,并且所有的类型都是由一种最基本的类型派生出的子
类型。C. Strachey指出,多态是一种参数化的多态,而重载则是一种特殊的多态
。用以判断不同的重载函数的机制就是函数标示(function signature)。
 
 重载在下面的例子中显得很有用:
 max( int, int )
 max( real, real )
 
 这将确保相对于类型int和real的最佳的max函数实体被调用。但是,面向对象的
程序设计为该函数提供了一个变量,对象本身被被当作一个隐藏的参数传递给了函
数(在C++中,我们把它称为this)。由于这样,在面向对象的概念中又隐式地包
含了一种对等的但却更有更多限制的形式。对于上述讨论的一个简单例子如下:
 int i, j;
 real r, s;
 i.max(j);
 r.max(s);
 
 但如果我们这样写:i.max(r),或是r.max(j),编译器将会告诉我们在这其中存
在着类型不匹配的错误。当然,通过重载运算符的操作,这样的行为是可以被更好
地表达如下:
 i max j 或者
 r max s
 但是,min和max都是特殊的函数,它们可以接受两个或者更多的同一类型的参数
,并且还可以作用在任意长度的数组上。因此,在Eiffel中,对于这种情况最常见
的代码形式看起来就像这样:
 il:COMPARABLE_LIST[INTEGER]
 rl:COMPARABLE_LIST[REAL]
 
 i := il.max
 r := rl.max
 
 上面的例子显示,面向对象的编程典范(paradigm),特别是和范型化
(genericity)结合在一起时,也可以达到函数重载的效果而不需要C++中的函数重
载那样的声明形式。然而是C++使得这种概念更加一般化。C++这样作的好处在于,
我们可以通过不止一个的参数来达到重载的目的,而不是仅使用一个隐藏的当前对
象作为参数这样的形式。
 
 另外一个我们需要考虑的因素是,决定(resolved)哪个重载函数被调用是在编译
阶段完成的事情,但对于重写来说则推后到了运行期间。这样看起来好像重载能够
使我们获得更多性能上的好处。然而,在全局分析的过程中编译器可以检测函数
min和max是否处在继承的最末端,然后就可以直接的调用它们(如果是的话)。这也
就是说,编译器检查到了对象i和r,然后分析对应于它们的max函数,发现在这种
情况下没有任何多态性被包含在内,于是就为上面的语句产生了直接调用max的目
标代码。与此相反的是,如果对象n被定义为一个NUMBER,NUMBER又提供一个抽象
的max函数声明(我们所用的REAL.max和INTERGER.max都是从它继承来的),那么编
译器将会为此产生动态绑定的代码。这是因为n既可能是INTEGER,也有可能是
REAL。
 
 现在你是不是觉得C++的这种方法(即通过提供不同的参数来实现函数的重载)很有
用?不过你还必须明白,面向对象的程序设计对此有着种种的限制,存在着许多的
规则。C++是通过指定参数必须与基类相符合的方式实现它的。传入函数中的参数
只能是基类,或是基类的派生类。例如:
 A.f( B someB ) {...}
 class B ...;
 class D : public B ...;
 A a;
 D d;
 a.f( d );
 其中d必须与类'B'相符,编译器会检测这些。
 
 通过不同的函数签名(signature)来实现函数重载的另一种可行的方法是,给不同
的函数以不同的名字,以此来使得它们的签名不同。我们应该使用名字来作为区分
不同实体(entities)的基础。编译器可以交叉检测我们提供的实参是否符合于指定
的函数需要的形参。这同时也导致了软件更好的自记录(self-document)。从相
似的名字选择出一个给指定的实体通常都不会很容易,但它的好处确实值得我们这
样去做。
 
 [Wiener95]中提供了一个例子用以展示重载虚拟函数可能出现的问题:
 class Parent
 {
  public:
   virutal int doIt( int v )
   {
    return v * v;
   }
 };
 
 class Child: public Parent
 {
  public:
   int doIt( int v, int av = 20 )
   {
    return v * av;
   }
 };
 
 int main()
 {
  int i;
  Parent *p = new Child();
  i = p->doIt(3);
  return 0;
 }
 
 当程序执行完后i会等于多少呢?有人可能会认为是60,然而结果却是9。这是因为
在Child中doIt的签名与在Parent中的不一致,它并没有重写Parent中的doIt,而
仅仅是重载了它,在这种情况下,缺省值没有任何作用。
 
 Java也提供了方法重载,不同的方法可以拥有同样的名字及不同的签名。
 
 在Eiffel中没有引入新的技术,而是使用范型化、继承及重定义等。Eiffel提供
了协变式的签名方式,这意味着在子类的函数中不需要完全符合父类中的签名,但
是通过Eiffel的强类型检测技术可以使得它们彼此相匹配。


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