废话不多说,这次讲的是 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,不然很容易造成程序的不稳定。

C++基本功之Operator的更多相关文章

  1. The Safe Navigation Operator (&.) in Ruby

    The most interesting addition to Ruby 2.3.0 is the Safe Navigation Operator(&.). A similar opera ...

  2. Blender 脚本之 Operator 初探

    addon(插件)用来扩展 Blender 的功能,跟其他软件里的 plugin(插件)一样,去掉不会影响软件的运行.插件可以加到 Blender 的用户偏好设置目录里,或者就在你所编辑的.blend ...

  3. Sequence Project Showplan Operator 序列映射运算符

    Sequence Project Showplan Operator 序列映射运算符 序列映射运算符会从一个已经排序的集合里通过不停添加集合里的列执行计算. 运算符根据一个或多个列的值把输入集合分为多 ...

  4. [c++] Operator overloading

    c++的操蛋属性:自己为一档,空一档,其他随意. UB_stack a; UB_stack b = a; // copy auto c = a; auto d {a}; // (or auto d = ...

  5. EC笔记:第二部分:11:在operator=中处理“自我赋值”

    已经一年半没有写过博客了,最近发现学过的知识还是需要整理一下,为知笔记,要开始收费了以前写在为知笔记上笔记也会慢慢的转到博客里. 话不多说,进入正题. 考虑考虑以下场景: 当某个对象对自身赋值时,会出 ...

  6. Swift学习(一):自定义运算符 operator

    自定义运算符仅能包含这些字符: / = - + * % < >!& | ^.~ 运算符位置: 前置运算符 prefix 中间运算符 infix 后置运算符 postfix 运算符其 ...

  7. Bash 4.4 中新增的 ${parameter@operator} 语法

    Bash 4.4 中新增了一种 ${...} 语法,长这样:${parameter@operator}.根据不同的 operator,它展开后的值可能是 parameter 这个参数的值经过某种转换后 ...

  8. 为什么operator>>(istream&, string&)能够安全地读入长度未知的字符串?

    一般而言,实现"读入用户输入的字符串",程序中自然不能对用户输入的长度有所限定.这在C++中很容易实现,而在C中确没那么容易. 这一疑问,我在刚学C++的时候也在脑中闪现过:不过很 ...

  9. tensorflow添加自定义的auc计算operator

    tensorflow可以很方便的添加用户自定义的operator(如果不添加也可以采用sklearn的auc计算函数或者自己写一个 但是会在python执行,这里希望在graph中也就是c++端执行这 ...

随机推荐

  1. mysql更新日志问题

    [root@localhost ~]# /etc/init.d/mysqld restart 停止 mysqld: [确定] 正在启动 mysqld: [确定] 故障:今天在维护以前数据库日志的时候, ...

  2. Lithium: HTML5 响应式的单页面模板

    在线演示:http://www.gbtags.com/gb/demoviewer/2507/837ac02e-4963-46c9-83ee-a0a0bb867f7f/3.-Lithium|app|in ...

  3. hdu 4287Intelligent IME(简单hash)

    Intelligent IME Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  4. 算法笔记_009:字符串匹配(Java)

    1 问题描述 给定一个n个字符组成的串(称为文本),一个m(m <= n)的串(称为模式),从文本中寻找匹配模式的子串. 2 解决方案 2.1 蛮力法 package com.liuzhen.c ...

  5. linux内核学习推荐书籍

    <UNIX环境高级编程>,推荐指数:★★★★★ <UNIX环境高级编程>是 Unix/ Linux 程序员案头必备的一本书籍.可以说,Linux 程序员如果没有读过这本书,就好 ...

  6. web站点,同一个浏览器只能登陆一个用户的原因(cookie不能跨浏览器)

    我的web站点,比如  http://ip/testsite/default.aspx, 当我在我的机器上,用chrome打开,用账号user1登陆,那么当我再新开个tab,再打开这个web站点,这时 ...

  7. QQ的未来在那里

    http://blog.sina.com.cn/s/blog_53bcb13e0100030g.html 早期的QQ非常清爽,没有广告,友好的界面,飞快的连接速度,为中国人量身订做的体贴功能,非常吸引 ...

  8. [Asp.net]缓存之页面缓存,控件缓存,缓存依赖

    写在前面 上篇文章介绍了缓存的基本概念及用途,另外也举了一个简单的例子,数据缓存(将一些耗费时间的数据加入到一个对象缓存集合中,以键值的方式存储.可以通过使用Cache.Insert()方法来设置缓存 ...

  9. 工作总结 @{var sas = String.Format("{0:yyyy-MM-dd}", Model.DemandTime.GetValueOrDefault());}

      可空DateTime类型 无法Tostring 或者 格式化时间格式   需要转换 为DateTime Nullable<T>.GetValueOrDefault() 方法 返回对应的 ...

  10. 学习C#——性能计数器

    写在前面: 作为Web应用开发前线的一枚小兵,每看到“性能”一词总有种要亮瞎眼的感觉,说到“性能”那就不能不提“数据”,在程序猿.攻城师中不是流行这样一句话吗?“无图无真相”,谁要说谁开发的应用性能有 ...