Chinaunix首页 | 论坛 | 博客
  • 博客访问: 215692
  • 博文数量: 35
  • 博客积分: 1480
  • 博客等级: 上尉
  • 技术积分: 390
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-14 14:27
文章分类

全部博文(35)

文章存档

2008年(35)

我的朋友

分类: C/C++

2008-05-05 17:51:43

小技巧:operator+ 一般可以间接的通过operator+=来实现

class Matrix {
public:
    Matrix& operator+=(const Matrix& rhs) {
        // ...
        return *this;
    }

    const Matrix operator+ (const Matrix& rhs) const {
        Matrix tmp(*this);      // 调用拷贝构造函数,通过复制产生一个新的object
        tmp += rhs;             // 利用operator += 实现operator +
        return tmp;             // 返回计算结果
    };
};





看下面的代码:
int func(int j) {
    int i = 30;
    i += j;
    return i;
}
这个函数很简单,大家也都认为它没有语法错误。函数返回时,变量i会被销毁,但是函数仍然成功的将i的值返回出来了。呵呵,有点神奇。类比一下:
    const Matrix operator+ (const Matrix& rhs) const {
        Matrix tmp(*this);      // 调用拷贝构造函数,通过复制产生一个新的object
        tmp += rhs;             // 利用operator += 实现operator +
        return tmp;             // 返回计算结果
    };
函数返回时,变量tmp会被销毁,但是函数仍然可以成功的将tmp的值返回出来。

注意这句return tmp;其中所隐藏的程序行为。函数返回一个值时,先把要返回的值复制一份(调用copy constructor,拷贝构造函数),放到别处,然后再销毁函数中各种需要销毁的变量,再将控制权交还给函数的调用者,调用者看到的返回值实际上是复制的值。

举例来说。比如我写
Matrix m = m1 + m2;
则m1+m2会调用Matrix::operator +(函数),函数返回时,先将返回值复制一份(调用copy constructor,拷贝构造函数),然后销毁函数中的变量tmp。函数返回后,利用复制好的值对变量m进行构造。当Matrix m = m1 + m2;这一整条语句都执行完毕后,m1+m2得到的值因为是临时值,不再使用,所以进行销毁,也就是说在这个时候把复制的值销毁。
这样看来,短短的一句Matrix m = m1 + m2;就要产生多个对象并进行销毁。(第一个:operator +中的tmp对象,第二个:返回时需要复制产生一个临时对象,第三个:正式需要的对象m)构造函数和析构函数被大量的调用,效率较低。这种情况下,编译器一般会对其进行优化,尽可能产生较少的对象,减少构造函数和析构函数的调用。但是最终要产生多少对象,这完全取决于编译器的优化能力:有的编译器可能完全不优化,产生三个对象;有的编译器则只产生两个对象(返回时复制一份的对象本身就是上一层函数的变量m);最理想的情况就是只产生一个对象(变量tmp,返回时复制一份的对象,上一层函数的变量m三者合一)。
可以做这样一个实验,定义一个类,在构造函数、拷贝构造函数、析构函数中分别输出不同的信息(例如cout << "construct" << endl;),然后定义一个函数来返回这个类的对象。可以看到不同的编译器可能输出不同的结果。

#include <iostream>
using namespace std;
class Test {
public:
    Test()            { cout << "construct" << endl; }
    Test(const Test&) { cout << "copy construct" << endl; }
    ~Test()           { cout << "descruct" << endl; }
};
Test func() {
    Test t;
    return t;
}
int main() {
    Test t = func();
    return 0;
}


我用vc2005测试,debug模式构造了两个对象(输出四行),release模式则只构造了一个对象(输出两行)。

注意以下几种用法是错误或者不推荐的。
1. 返回即将被销毁的变量的引用。错误的做法。因为变量被销毁,引用它也就没有意义。
2. 返回指向即将被销毁的变量的指针。错误的做法。理由同上。
3. 通过new分配内存并创建对象,返回它的引用或返回指向它的指针。不推荐。例如:
Matrix m = (m1 + m2) + m3;
则m1+m2的结果一直保存着,无法进行释放。
必须写:
Matrix* pTemp1 = &(m1 + m2);
Matrix* pTemp2 = &(*pTemp1 + m3);
m = *pTemp2;
delete pTemp1;
delete pTemp2;
这种烦琐的方式显然不是我们想要的。
4. 定义一个static变量,返回它的引用或返回指向它的指针。不推荐。
Matrix m = m1 + m2;           // 没问题
Matrix m = (m1+m2) + (m3+m4); // 错误
因为用了static,每次相加的结果都是放到同一个变量中进行保存的。C++没有规定到底先计算m1+m2还是先计算m3+m4。如果先计算m1+m2,则后来计算m3+m4时就会把m1+m2的结果覆盖;如果先计算m3+m4,则后来计算m1+m2时就会把m3+m3的结果覆盖,无论怎么计算,最后都无法得到正确的结果。

引用
不过,拷贝构造函数,operator =操作符,还有析构函数一般都要一起出现。
假如其中一个可以不要,那么一般三个都可以不要。


如果不定义自己的拷贝构造函数、operator =、析构函数,则C++编译器会自动产生一个默认的。实际使用时,先考虑使用默认的拷贝构造函数、operator=、析构函数是否满足需要,如果不满足,就自己定义之。通常,如果成员中使用了指针,就需要自己定义。

最后,operator +返回的类型要加上const。这是为了避免出现下面的情况:
(m1 + m2) = m3;
如果m1+m2是一个Matrix类型,则它可以被赋值(调用operator=),但是上面的语句是不合逻辑的,不应该被允许。只要让m1+m2是一个const Matrix类型,就可以让上面的语句编译出错。从而避免这种不合逻辑的写法。



小结:
编写加法运算符,最合理的实现可能就是这个了:
    const Matrix operator+ (const Matrix& rhs) const {
        Matrix tmp(*this);      // 调用拷贝构造函数,通过复制产生一个新的object
        tmp += rhs;             // 利用operator += 实现operator +
        return tmp;             // 返回计算结果
    };
程序必须先追求正确,然后才能追求高效。从上面四种不推荐的情况来看,返回引用或者指针都不是好的做法(有些做法是错误的,有些做法隐含了潜在错误),因此只好返回实际的对象了。
通过编译器的优化,返回一个对象的效率并没有大家想象的那么不堪,实际构造对象的个数往往并不多。没有必要为了这点性能去担忧。

阅读(4781) | 评论(0) | 转发(0) |
0

上一篇:cin与cout

下一篇:数据结构第5章(多项式)

给主人留下些什么吧!~~