More Effective C++精简版(4续)

6.5
      从效率的观点来看,你不应该关心函数返回的对象,你仅仅应该关心对象的开销。

inline const Rational operator*(const Rational& lhs,

                                const Rational& rhs)

{

 return Rational(lhs.numerator() * rhs.numerator(),

                  lhs.denominator() * rhs.denominator());

}

Rational的构造函数被调用, 这是一个临时对象,函数把它拷贝给函数的返回值

C++规则允许编译器优化不出现的临时对象(temporary objects out of existence)。因此如果你在如下的环境里调用operator*:

Rational a = 10;

Rational b(1, 2);

Rational c = a * b;                          // 在这里调用operator*

         编译器就会被允许消除在operator*内的临时变量和operator*返回的临时变量。它们能在为目标c分配的内存里构造return表达式定义的对象。如果你的编译器这样去做,调用operator*的临时对象的开销就是零:没有建立临时对象。你的代价就是调用一个构造函数――建立c时调用的构造函数。而且你不能比这做得更好了,因为c是命名对象,命名对象不能被消除

        它甚至还有一个名字:返回值优化. 但注意,这种优化对普通的赋值运算无效,编译器不能够用拷贝构造函数取代赋值运算动作.
 

6.6

         用函数重载来消除类型转换

         C++中有一条规则是每一个重载的operator必须带有一个用户定义类型(user-defined type)的参数

         利用重载避免临时对象的方法不只是用在operator函数上。比如在大多数程序中,你想允许在所有能使用string对象的地方,也一样可以使用char*,反之亦然。同样如果你正在使用numerical(数字)类,例如complex(参见条款M35),你想让int和double这样的类型可以使用在numerical对象的任何地方。因此任何带有string、char*、complex参数的函数可以采用重载方式来消除类型转换。

          不过,必须谨记80-20规则,没有必要实现大量的重载函数

6.7

         operator+、operator=和operator+=之间没有任何关系,因此如果你想让这三个operator同时存在并具有你所期望的关系,就必须自己实现它们。同理,operator -, *, /, 等等也一样。

         确保operator的赋值形式(assignment version)(例如operator+=)与一个operator的单独形式(stand-alone)(例如 operator+ )之间存在正常的关系,一种好方法是后者(指operator+)根据前者(指operator+=)来实现

第一、   总的来说operator的赋值形式比其单独形式效率更高,因为单独形式要返回一个新对象,从而在临时对象的构造和释放上有一些开销

第二、   提供operator的赋值形式的同时也要提供其标准形式,允许类的客户端在便利与效率上做出折衷选择

第三、   注意使用返回值优化

template<class T>

const T operator+(const T& lhs, const T& rhs)

{

 T result(lhs);                        // 无法享受编译器的优化

 return result += rhs;                

}
 

6.8
     一旦你找到软件的瓶颈(通过进行profile 参见条款M16),你应该知道是否可能通过替换程序库来消除瓶颈

     比如如果你的程序有I/O瓶颈,你可以考虑用stdio替代iostream,如果程序在动态分配和释放内存上使用了大量时间,你可以想想是否有其他的operator new operator delete的实现可用。因为不同的程序库在效率、可扩展性、移植性、类型安全和其他一些领域上蕴含着不同的设计理念,通过变换使用给予性能更多考虑的程序库,你有时可以大幅度地提高软件的效率。

6.9
        在程序中的每个类只要声明了虚函数或继承了虚函数,它就有自己的vtbl,并且类中vtbl的项目是指向虚函数实现体的指针

        如果有一个C2类继承自C1,重新定义了它继承的一些虚函数,那么它的virtual table项目指向与对象相适合的函数

虚函数所需的第一个代价:你必须为每个包含虚函数的类的virtual talbe留出空间

                        第二个代价是:在每个包含虚函数的类的对象里,你必须为额外的指针付出代价。

调用虚函数所需的代价基本上与通过函数指针调用函数一样。虚函数本身通常不是性能的瓶颈。

在实际运行中,虚函数所需的代价与内联函数有关。实际上虚函数不能是内联的。这是因为“内联”是指“在编译期间用被调用的函数体本身来代替函数调用的指令,”但是虚函数的“虚”是指“直到运行时才能知道要调用的是哪一个函数。”

                        第三个代价:你实际上放弃了使用内联函数

多继承经常导致对虚基类的需求

对象和类的有关信息,存储在类型为type_info的对象里,使用typeid操作符访问一个类的type_info对象。

下面这个表各是对虚函数、多继承、虚基类以及RTTI所需主要代价的总结:

Feature

Increases
Size of Objects

Increases
Per-Class Data

Reduces
Inlining

Virtual Functions

Yes

Yes

Yes

Multiple Inheritance

Yes

Yes

No

Virtual Base Classes

Often

Sometimes

No

RTTI

No

Yes

No

原文地址:https://www.cnblogs.com/ethan/p/675748.html