More Effective C++ 条款22 考虑以操作符复合形式(op=)取代其独身形式(op)

1. 一般来说,重载了算数操作符(以下称"独身形式"),那么也就要重载复合赋值操作符(以下称"复合形式").要确保操作符的复合形式例如(operator+=)和独身形式(例如operator+)行为相一致,基于前者实现后者是一个好方法.例如:

class Rational{
public:
    Rational operator+=(const Rational&);
   ...
}
Rational operator+(const T&lhs,const T&rhs){
    return T(lhs)+=rhs;
}

2. 操作符的复合形式通常比其独身形式效率更高:独身形式需要返回新对象,因而需要承担临时对象的构造和析构成本,复合形式直接将结果写入右端变量,不需要临时对象的构造过程,因此以下使用独身形式的写法:

Rational a,b,c,d;
...
Rational result=a+b+c+d;

效率没有使用复合形式的写法高:

Rational a,b,c,d;
Rational result=a;
result+=b+=c+=d;

因为前者使用operator+产生了3个临时对象,即使有RVO(返回值优化,见条款21),那也只是发生在最后一个operator+,而前两个operator+构造的临时对象不会被优化掉(稍后会解释,如果按照1中operator+的实现,即使是最后一个operator+产生的临时对象也不会被优化,因此临时对象总共有3个而不是2个).

前者易编写,维护,调试而后者效率较高,对汇编程序员比较直观.

3. 《More Effective C++》解释了1中operator的实现方法为

Rational operator+(const T&lhs,const T&rhs){
    return T(lhs)+=rhs;
}

的原因(为了便于说明,称以上实现为版本1),声称返回匿名对象比返回具名对象更容易使编译器实行RVO,但所谓的"返回匿名对象比返回具名对象更容易使比编译器实行RVO"是建立在两个基础之上的:1.NRVO(具名返回值优化,见条款21)还未出现;2.返回匿名对象,return语句只返回一个匿名对象,而不再对其进行其他操作.

对于以下操作:

Rational a,b;
Rational c=a+b;

operator+的版本1实际上是无法完成RVO的:版本的最后一步是return T(lhs)+=rhs,返回的不是临时对象T(lhs),而是临时对象调用operator+=后的结果,此时编译器不知道谁才是要返回的结果(T(lhs)这个临时对象显然不是,因为还有后续操作),因此只能老老实实的对T(lhs)调用operator+=,再将临时对象的结果拷贝给c,临时对象的产生无法避免.

也就是说,《More Effective C++》所提倡的这种方法现在来看已经不合时宜了,在NRVO已经普及的今天,operator+的以下实现(称版本2)反而更能帮助编译器实现优化:

Rational operator+(const Rational&lhs,const Ration&rhs){
    Rational temp(lhs);
    temp+=rhs;
    return temp;
}

以下是实验代码以及Win7-32,Visual Studio 2013 release模式下的结果:

#include<iostream>
using namespace std;
class Rational{
public:
    Rational& operator+=(const Rational&rhs){
        numerator += rhs.numerator;
        denominator += rhs.denominator;
        return *this;
    }
    Rational(const Rational&rhs){
        cout << "copy constructor" << endl;
    }
    Rational() :numerator(0), denominator(0){ cout << "constructor" << endl; }
private:
    int numerator;
    int denominator;
};
Rational operator+(const Rational& lhs, const Rational& rhs){
    return Rational(lhs)+=rhs;
}
int main(){
    Rational a, b;
    Rational c = a + b;
    system("pause");
    return 0;
}

运行结果为

共调用了两次copy constructor,可见并没有实行优化.

operator+改为实现2后:

Rational operator+(const Rational& lhs, const Rational& rhs){
    Rational temp(lhs);
    temp += rhs;
    return temp;
}

实验结果为:

只调用了一次copy constructor,可见直接将operator+中的temp替换为外层的c,即实行了NRVO优化.

以上实验的详细解释可参照http://www.xuebuyuan.com/1595871.html,由于实验平台不同,实验结果会有出入,但是中心思想是相同的.

时间: 2024-10-28 05:24:13

More Effective C++ 条款22 考虑以操作符复合形式(op=)取代其独身形式(op)的相关文章

Effective C++ 条款22

将成员变量声明为private 本节条款,作者花了很大的篇幅去介绍,可是我感觉就学到一句话. 那就是注意程序的封装性. 程序的封装性起到什么作用? 很明显是保护数据操作的安全性以及增强以后程序的可维护性. 将类的数据成员声明为private的,可以保护数据不被随便修改.大家都明白权限等级就是安全等级. 至于程序的可维护性,就是用良好的函数接口代替直接的数据成员的操作,这样不但编写的时候方便,而且在修改程序计算过程的时候,只需要修改函数接口内部程序,而且不需要全部重新编译.

Effective C++ 条款22 将成员变量声明为private

1. 设计类时,应该将成员变量声明为private,尽量避免用户对成员变量的直接访问,使用户只能通过函数接口访问成员,这样利于实现封装,特别是当类需要改变(添加或减少成员变量)时,只需要改变接口实现,对于用户来说只需要重新编译即可(否则类的用户需要修改大量对成员变量进行访问的代码) 2. protect并不比private更具封装性,特别是涉及到继承时,因此访问权限根据封装新只有两种:private和其他.

Effective C++:条款22:将成员变量声明为private

(一)为什么不采用public成员变量 (1)首先,从语法一致性考虑,客户唯一能访问对象的方法就是通过成员函数,客户不必考虑是否该记住使用小括号(). (2)其次,使用函数可以让我们对成员变量的处理有更精确的控制.如果我们令成员变量为public,那么每个人都可以读写它! 但如果我们以函数取得或设定其值,我们就可以实现出"不准访问"."只读访问"以及"读写访问",我们甚至可以实现"惟写访问". class AccessLeve

Effective C++ 条款九、十 绝不在构造和析构过程中调用virtual函数|令operator=返回一个reference to *this

  1.当在一个子类当中调用构造函数,其父类构造函数肯定先被调用.如果此时父类构造函数中有一个virtual函数,子类当中也有,肯定执行父类当中的virtual函数,而此时子类当中的成员变量并未被初始化,所以无法调用子类与之对应的函数.即为指向虚函数表的指针vptr没被初始化又怎么去调用派生类的virtual函数呢?析构函数也相同,派生类先于基类被析构,又如何去找派生类相应的虚函数? 2.做法:将子类的某个函数改为non-virtual,然后在子类构造函数中传递参数给父类函数.然后父类的构造函数

Effective C++ 条款11,12 在operator= 中处理&ldquo;自我赋值&rdquo; || 复制对象时不要忘记每一个成分

1.潜在的自我赋值     a[i] = a[j];     *px = *py; 当两个对象来自同一个继承体系时,他们甚至不需要声明为相同类型就可能造成别名. 现在担心的问题是:假如指向同一个对象,当其中一个对象被删,另一个也被删,这会造成不想要的结果. 该怎么办? 比如:   widget& widget:: operator+ (const widget& rhs) {    delete pd;    pd = new bitmap(*rhs.pb);    return *thi

Effective C++ 条款15、16 在资源管理类中提供对原始资源的访问||成对使用new 与 delete要采取相同形式

1.在资源管理类中提供对原始资源的访问     前几个条款很棒,它们是对抗资源泄露的壁垒,但很多APIs直接指向 资源,这个时候,我们需要直接访问原始资源.     这里,有两种方法解决上述问题,我们可将RAII对象转换为原始资源.通过 显式转换与隐式转换.     通常,tr1:: shared_ptr 和 auto_ptr 都提供一个get成员函数,用来执行显式转换,也就是返回智能指针内部的原始指针的复件.因为它也重载了指针取值操作符* –>.当然也可以通过隐式转换为底部原始指针.     

More Effective C++ 条款0,1

More Effective C++ 条款0,1 条款0 关于编译器 不同的编译器支持C++的特性能力不同.有些编译器不支持bool类型,此时可用 enum bool{false, true};枚举类型来模拟bool类型.这允许参数类型为int和bool的函数重载,但是这样做的缺陷是,对于内置的比较运算符,其仍返回int类型. f(int);f(bool); f(a < b); // 会调用f(int),但其实用户期望调用f(bool). 但是一旦改用支持bool类型的编译器,情况可能会发生改变

More Effective C++ 条款35 让自己习惯于标准C++ 语言

(由于本书出版于1996年,因此当时的新特性现在来说可能已经习以为常,但现在重新了解反而会起到了解C++变迁的作用) 1. 1990年后C++的重要改变 1). 增加了新的语言特性:RTTI,namespaces,bool,关键词mutable和explicit,enums作为重载函数之自变量所引发的类型晋升转换,以及"在class 定义区内直接为整数型(intergral) const static class members设定初值"的能力. 2). 扩充了Templates的特性

effective c++ 条款4 make sure that objects are initialized before they are used

1 c++ 类的数据成员的初始化发生在构造函数前 class InitialData { public: int data1; int data2; InitialData(int a, int b) { data1 = a: //this is assignment data2 = b; //this is assignment } /* InitialData(int a, int b):data1(a),data2(b) //this is initial {} */ } 2 不同cpp文