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 |
Increases |
Reduces |
Virtual Functions |
Yes |
Yes |
Yes |
Multiple Inheritance |
Yes |
Yes |
No |
Virtual Base Classes |
Often |
Sometimes |
No |
RTTI |
No |
Yes |
No |