游戏开发论坛

 找回密码
 立即注册
搜索
查看: 8139|回复: 6

C++基本功和 Design Pattern系列(5) Operator 下

[复制链接]

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
发表于 2006-11-17 07:15:00 | 显示全部楼层 |阅读模式
======================================================
大家请把我的文章当参考,详细内容  还请参照 权威书籍
<c++ programming language>如果文中有错误和遗漏,
请指出,Aear会尽力更正, 谢谢!
======================================================

继续上一章的内容,下面是经过调整后的Test Class代码:

class Test {
private:
    int internalData;
public:
    // constructor and destructor
    Test(int data = 0) : internalData(data) {};
    Test(const Test & Para) : internalData(Para.internalData) {};
    ~Test() {};
  
    // Operator overlording
    Test & operator += (const Test & Para1);
    Test operator + (const Test & Para1);
};

Test & Test:perator += ( const Test & Para1 )
{
    internalData += Para1.internalData;
    return * this;
}

Test Test::operator + (const Test & Para1)
{
    return Test(*this) += Para1;
}
下面我们假设要给这个Test Class添加一种新的功能,让Test Class 和 Integer之间能够进行加法操作。 也就是说可以执行下列代码:

Test x1(10);
x1 = x1 + 5;
x1 += 5;

实际上,我们不需要进行任何修改,上面的代码就能够正确执行。因为我们提供的构造函数Test(int data = 0) 能够隐性的 (implicit type conversion) 把一个integer 转换成一个Temporary Test Object,然后掉用 Test Test::operator + (const Test & Para1)。因此,上面的代码等同于:

x1 = x1.operator + (Test(5));
x1 = x1.operator += (Test(5));

Implicit Type Conversion 实际上会带来很多的麻烦,要想避免潜在的危险,最好在Test(int data = 0)前面加上explicit,表示如果对interger转换成Test,必须由程序员来控制,compiler不得进行隐性的操作。因此,要想似的 x1 = x1 + 5能够正常运行,有2种方法:

x1 = x1 + static_cast<Test>(5);

x1 = x1 + Test(5);

还有一点需要注意的是,如果不用explicit type conversion,可以运行:

x1 = x1 + 5;

但是在编译:

x1 = 5 + x1

的时候就会报错了,除非使用一个Temporary Object ,如:

x1 = Test(5) + x1;

要想使Test Class 支持 x1 = 5 + x1,最好的方法就是用helper function. 下面我们来看看Operator的另外一中定义方式。

==================分割线==================

我们可以使用friend function 来定义Test Class 的加法运算,代码如下:

class Test {
    Test(int data = 0) : internalData(data) {};
    ...
    // 针对这个Test Class, 并不需要下面这行。
    friend Test operator + ( const Test & Para1, const Test & Para2);
};

Test operator + ( const Test & Para1, const Test & Para2)
{
    return Test(Para1) += Para2;
}

首先我们需要注意的是,Test(int data = 0)没有用explicit,也就是说可以进行隐性的类型转换,因此无论是运行:
    x1 = x1 + 5;
还是:
    x1 = 5 + x1;
都能够编译通过。

解决了基本的功能问题,让我们继续考虑一下效率。无论是在x1 = x1 + 5,还是在x1 = 5 + x1,都至少会掉用额外的constructor和destructor把5转换成Test Object,这种浪费是很没有必要的。其次允许compiler进行implicit type conversion并不是一个良好的习惯。解决这些问题的方法,就是提供专用的 operator + 来进行integer和Test object之间的加法操作,具体代码如下:

========== 支持 x1 + 5 ==========
Test operator + ( const Test & Para1, int Para2)
{
    return Test(Para2) += Para1;
}

========== 支持 5 + x1 ==========
Test operator + ( int Para1, const Test & Para2 )
{
    return Test(Para1) += Para2;
}

同时还要在class Test中加如下面2行(对于此例子并不需要,不过正常情况是需要的):

friend Test operator + ( int Para1, const Test & Para1 );
friend Test operator + ( const Test & Para1, int Para2 );

并且在constructor前面加上 explicit。当然,你也可以用Template进行定义,如下:

========== 支持 x1 + 5 ==========
template <class T>
T operator + ( const T & Para1, int Para2)
{
    return T(Para2) += Para1;
}

实际上对于 template的定义,我个人并不推荐. 首先是因为namespace的问题,到底是global namespace呢?还是一个local namespace?如果是global namespace,那不一定所有的global class 都需要 operator +,这样就提供了多余的class操作。local namespace倒是可以用,前提是所有的class都定义了 +=. 也许对于大多数class来讲,并不需要operator + 的操作。所以我觉得对于 operator 的定义,尽量少用 template (个人观点).

==================分割线==================

下面说说关于类型转换的operator. 对于一个Abstract Data Type来说,类型转换是经常用到的,比如我们前面提到的从 integer转换成 Test, 可以使用implicit type conversion 和 explicit type conversion. 但是如果我们想从Test 转换成 integer,compiler无法支持自动的类型转换,因此需要我们提供相应的operator:

class Test {
    ...
    // Type converstion from Test to int
    operator int() { return internalData; };
}

那么我们就可以执行:
    int i = Test(10);

实际上,operator int()又是一种implicit type conversion,这并是收程序员的控制。良好的程序设计,是programmer能够精确的控制每一个细微的操作。因此并不推荐使用 operator int(),好的方法是按照 < effective c++ > 中给出的那样,提供一个asInt() method,来做explicti type conversion:

============ explicti ============
class Test {
    ...
    // Type converstion from Test to int
    int asInt() { return internalData; };
}

================== Test++ & ++Test ==================

相信大家都知道 Prefix ++ 和 Postfix ++的区别是什么,下面是代码:

// Prefix
Test& operator++()
{
    ++internalData;
    return (*this);
}

// Postfix
Test operator++(int)
{
    ++*this;
    return --Test(*this); // 为了使用返回值优化,需要定义 --Test
}

我们只是简单的看下效率问题,在 Prefix中也就是 ++ 返回的是reference,没有temporary object,在 Postfix中返回的是个object,使用了Temporary。相信大家都知道了,能不使用 Test++的地方就不要使用,尽量使用 ++Test。

比如:

for( iterator i = XXX; XXX; ++i) // 不要使用 i++

对于postfix, compiler并不能保证肯定会优化成 prefix,所以写代码的时候尽量注意。

================== 其他关于Operator ==================

有些operator,并不推荐进行overload,因为会出现无法预料的情况。这些operator 包括:

&&, || , & , |  , ","

举个简单的例子,如果你overload了",",那么有一个for循环如下:

for( Test x1 = x2,i = 0; ; ) {....}

到底是x1 = x2 和 i = 0呢?还是 x1 = x2.operator , (i) = 0 呢?如果overload了 & ,对于逻辑判断,x1 && x2,到底是  x1 && x2呢?还是 x1.operator & (&x2)呢?因此这些overload都会产生很多让人费解的问题。

其次,很多operator overload需要很小心的对待,这些operator 如下:

new new[] delete delete[] -> [] ()

请仔细阅读 C++ 标准,了解详细内容后,再对这些operator进行overload,不然很容易造成程序的不稳定。

好了,关于operator 就说这么多了,欢迎大家有空去我的Blog坐坐http://blog.sina.com.cn/u/1261532101下次见。

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-11-17 10:33:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(5) Operator 下

几点建议:
1. Test(int data = 0) : internalData(data) {};
    个人建议即使再短的句子也不要写在一行,虽然看上去比较紧凑,但是无法设置断点。

2. Test operator + ( const Test & Para1, const Test & Para2)、Test operator + ( const Test & Para1, int Para2) 和 Test operator + ( int Para1, const Test & Para2 )
    照文中的实现,其实本质上与compiler的实现并没有什么区别,效率同样低下。正确的做法应该是
Test operator + ( const Test & Para1, int Para2)
{
    Test tmp(Para1);
    tmp.internalData += Para2;
    return tmp;
}
    而不是为了return by value而放弃了本应有的效率,顾此而失彼或执念太深都是不好的。而且像之前某贴中某人说过的,现在的compiler已经有足够的能力将上面的代码进行优化了。

3. template的operator + 设计更是糟糕,这样写需要假设class Test有能力处理所有类型,既然都考虑到ctor前要加explicit的层次,为何这里还会犯这样的错误,未来势必会导致更多的问题。

4. 对于Test operator++(int)中return --Test(*this);这段代码实在是奇怪和难懂,道理同2。我们为什么要放弃我们当前可以把握的良好的可读性高的代码不要,而去追求return by value这个虚无缥缈的东西呢。还是遵照圣经中的标准写法吧。
// Postfix
Test operator++(int)
{
    Test tmp(*this);
    ++internalData;
    return tmp;
}

PS,记得C++他爹曾经评价loki时说这个库是过于聪明人写出来的过于聪明的东西。务实还是务虚,这是一个在设计时需要考虑的问题。

121

主题

2029

帖子

2034

积分

金牌会员

Rank: 6Rank: 6

积分
2034
QQ
发表于 2006-11-17 18:26:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(5) Operator 下

然后boost也是一样的聪明。

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
 楼主| 发表于 2006-11-18 07:39:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(5) Operator 下

for comment 1. I just hate typing in a bunch of spaces for this web layout
for comment 2. The temporary object will be optimized away
for comment 3. I did say that using template is not recommended.
for comment 4. I'm just paranoia about the speed, and don't trust compilers.

Follow BZ comments if you really want to have clean, readable code.

8

主题

553

帖子

560

积分

高级会员

Rank: 4

积分
560
发表于 2006-11-18 09:59:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(5) Operator 下

Don't be paranoia, check everything you doubt against compiler generated asm code, remember: nowaday compilers are always more clever than you think.

0

主题

17

帖子

17

积分

新手上路

Rank: 1

积分
17
发表于 2006-11-23 11:50:00 | 显示全部楼层

Re: C++基本功和 Design Pattern系列(5) Operator 下

@千里马肝

几点建议:
1. Test(int data = 0) : internalData(data) {};
    个人建议即使再短的句子也不要写在一行,虽然看上去比较紧凑,但是无法设置断点。

这点我深有体会,建议大家不要这么写,调试比较麻烦。

1

主题

18

帖子

18

积分

新手上路

Rank: 1

积分
18
发表于 2006-11-23 14:49:00 | 显示全部楼层

Re:C++基本功和 Design Pattern系列(5) Operator 下

在坚持看
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-25 23:32

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表