废话不多说,这次讲的是 Operator overload. 关于operator, 在 < The C++ Programing Language > 里的描述,可以用做overload的如下:

+ * / % ^ & | ~ ! = < > += = *= /= %= ^= &= |= << >> >>= <<= == != <=
>= && || ++ >* , > [] () new new[] delete delete[]

先从 operator = 说起。上章中Aear已经提到过,这里再次强调下,只要是在class member中有 pointer的存在,class 就应该提供 copy constructor 和 operator =.

除了 operator =以外,最常用的operator就是 + - * / += -+ *= /= ++ --。 由于四则运算大同小异,我们就拿简单的 + 来举例子说明。让我们看下下面的这段代码有什么问题:

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::operator += ( const Test & Para1 )
{
    internalData += Para1.internalData;
    return * this;
}

Test Test::operator + (const Test & Para1)
{
    Test result;
    result.internalData = internalData + Para1.internalData;
    return result;
}

首先让我们比较一下 += 和 + 两个operator的效率,我们可以看到,在"+="中,返回的是 *this的reference,而在+中,返回的是一个临时创建的 result,并且 result object在返回的过程中,由于是return by value,因此compiler会掉用copy constructor把result拷贝到返回值中,然后掉用 result.~test()销毁掉这个临时变量。也就是说,使用 + 比 += 要产生更多的overhead。在某些效率第一的情况下,临时变量的constructor和destructor都是非常昂贵的操作。比如:

=============one temporary object=============
Test x1 = x2 + x3;

为了提高效率,我们可以这么写:

=============no temporary object=============
Test x1(x2);
x1 += x3;

简单一点的表达式有可能会被compiler优化掉,但是复杂的表达式,只能才用手动的形式优化。

让我们再仔细的看下 Test Test::operator + (const Test & Para1) 这个函数。如果考虑程序的具体实现,我们可以看出来 operator += 和 operator +没有本质的却别,但是如果我们需要改动 Test + 的操作,就必须同时更改 operator += 和 operator + 实现,对同一种功能在几处不同的地方进行维护,并不是良好的设计,因此,我们应该对 Test Test::operator + (const Test & Para1) 做如下改动,使得程序更加容易维护:

=============easy to maintain=============
Test Test::operator + (const Test & Para1)
{
    Test result(*this);
    result += Para1;
    return result;
}

可以看到在operator +里调用了 +=的操作,因此,如果我们需要给加法赋予不同的含义,只需要更改 operator += 就足够了。

让我们继续深入的看 Test Test::operator + (const Test & Para1)。值得强调的是,无论如何temporary object是必须存在的,无数大师的经验证明,想用static member, dynamic memory等方法消除掉 temporary object 的 construction 和 destruction,都会产生这样或者那样的逻辑和程序错误。

但是我们仍然可以利用compiler来对这个temporary object进行优化。对于Test Test::operator + (const Test & Para1) 这样的操作,compiler会传递给 operator + 一个 temporary object, 如果 operator +里的代码合适,那么compiler就会用这个temporary object 代替程序里创建的temporary object,从而减少了constructor和destructor的掉用。经过compiler优化后的pseudocode如下:

Test::operator + (Test & Temporary, const Test & Para1)
{
    Temporary.internalData = internalData + Para1.internalData;
    return;
}

这样减少了temporary object 的construction 和 destruction,效率就会提高很多,但是要想使compiler能够进行 return by value的优化,必须满足2个条件:

1. class 必须有 copy constructor
2. 在代码中明确表示,返回的是个temporary object.

因此,出了提供copy constructor外,还必须对 operator + 进行适当的修改,最终代码如下:

==========Final Version for Maintenance and Optimization==========

Test Test::operator + (const Test & Para1)
{
    return Test(*this) += Para1;
}

在这个代码里边,最明显的区别就是没有result这个变量,而是返回一个临时创建的Test object,因此compiler就会知道这个函数可以用临时变量优化。

也许你会感到惊讶,不过这个operator +还不是最快速的。因为我们看到,在Test(*this) += Para1 的过程中,调用了一次constructor 一次 operator +=,但是已经足够了。不过为了效率,我们有更加极端的方法,在如下的代码中,我们舍弃了可读性,可维护性等等,只为了更快的速度:

==========糟糕的风格,不过更快==========
class Test {
    ...   
private:
    // cosntructor for operator +
    Test(int data, const Test & Para1 ) : internalData(data + Para1.internalData) {};
};

Test Test::operator + (const Test & Para1)
{
    return Test(internalData, Para1);
}

如果同时还要定义 operator - * /等操作的constructor,我们可以适当更改constructor的signature,从而可以用constructor实现不同的运算

关于operator第一部分今天就说这么多,大家有空去坐坐 http://blog.sina.com.cn/u/1261532101;下次见。

======================================================
 大家请把我的文章当参考,详细内容  还请参照 权威书籍 
 <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::operator += ( 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,不然很容易造成程序的不稳定。

最新文章

  1. MyEclipse 2015 Stable 2.0安装包及破解工具下载
  2. 刷新ALV定位到当前记录行
  3. python数据结构与算法——图的基本实现及迭代器
  4. iOS多线程技术
  5. ant的那些闹挺事
  6. 刻通云KeyTone Cloud测试
  7. IntelliJ IDEA 比较当前版本文件与历史文件
  8. 总结html5-canvas学习笔记
  9. nodejs中间层现实
  10. 在配置WCF服务的时候出现的错误总结
  11. 讲讲金融业务(一)--自助结算终端POS
  12. Typings实现智能
  13. JSON 分析数据格式
  14. 对于java中的&quot;\&quot;和&quot;/&quot;&#160;区别
  15. Django之ORM基础
  16. 2019年Python数据挖掘就业前景前瞻
  17. springMVC 处理json 及 HttpMessageConverter 接口
  18. Android Activity.startActivity流程简介
  19. rabbitMQ日常管理(转)
  20. 新人成长之入门Vue.js弹窗Dialog介绍(二)

热门文章

  1. 使用Visual Studio 2017编译opencv 3.2版本
  2. tftp的安装、设置以及put、get传输实验
  3. Servlet实现文件上传,可多文件上传
  4. ubuntu server激活即时通讯IM服务 Instant Messaging is not activated on this server
  5. 登陆Oracle EBS的Form遇到问题Internet Explorer has modified this page to help prevent cross-site scripting
  6. 如何下载flash离线安装包
  7. servlet awt随机图片验证码
  8. IntelliJ IDEA 学习(二):Intellij IDEA 创建Web项目并在Tomcat中部署运行IDEA
  9. Guid.NewGuid().ToString()得几种格式显示
  10. unity, editorWindow update计时