more
条款17:考虑使用lazy evaluation(缓式评估)
1. Reference Counting(引用计数)
string s1 = "Hello";
string s2 = s1;
在对s2只进行读操作时,s2只会和s1共享数据,只有对s2进行真正的写操作,资源拷贝才会发生。
2. 区分读和写
3. Lazy (缓式取出)
有些LargeObject的体积很大,欲取出此类对象的所有数据,数据库相关操作程序可能成本极高,Lazy (缓式取出)只有在真正访问对象的该数据时才会从数据库中加载。
利用此技巧可以实现出“demand-paged”式的对象初始化行为,即在初始化LargeObject时,成员变量字段赋空,只有在对成员变量进行操作时,该字段才会从数据库中读取。
4. Lazy Expression Evaluation(表达式缓评估)
不立刻计算表达式的值,只在需要时计算,可以用数据结构存储这种操作。
条款18: 分期摊还预期的计算成本(over-eager evaluation)
class Test{
public:
int min() const;
int max() const;
int avg() const;
...
};
eager evaluation 调用时立即评估 lazy evaluation 调用时存在某些数据结构中,只有在“这些函数的返回值真正需要被用到时”才计算  over-eager evaluation,随时记录数据集中的最小值,最大值,使用时立刻返回。
1. Catching
指将频繁访问的数据暂存在更快的存储层,以减少后续访问延迟。
2. Prefetching
Prefetching 指提前预测即将访问的数据,并将其主动加载到缓存中(空间局部性)
vectort初始化有10个元素,但其在new操作时往往会申请两倍以上的空间。避免数组扩张的成本,空间换时间。
条款19:了解临时对象的来源
临时对象是不可见的:不会在源码出现。
1.当隐式类型转换(implicit type conversions)被施行起来以求函数调用能够成功
1.1函数参数传值
func(MyClass()); // 这里的 MyClass() 是临时对象
1.2 对象被传递给一个reference-to-const参数时
char s[25];
int test(const string& str);
test(s) //可以通过编译,期间会产生一个string临时对象,str参数被绑定到该临时对象上
当对象被传递给一个reference-to-non-const参数时,这种转换并不会发生,例子如下:
char s[25];
int test( string& str);
test(s) //无法通过编译,如果产生一个string临时对象,str参数被绑定到该临时对象上,str的改动不会影响s,违背了引用的直觉
2.当函数返回对象的时候
条款20:协助完成“返回值优化(RVO,return value optimition)"
在早期 C++(C++98 时代),下面代码在直觉上会产生多个对象
class Widget {};
Widget makeWidget() {
return Widget();
}
Widget w = makeWidget();
如果按“语法表面理解”,似乎会发生:
- 构造一个临时
Widget - 拷贝到函数返回值临时对象
- 再拷贝到
w
看起来是:
1 次构造 + 2 次拷贝 + 若干析构
但编译器可以消除这些临时对象,只会发生一次构造。RVO 的核心思想是:
直接在调用者的内存空间中构造返回对象。
函数匿名对象和命名对象编译器都可以进行RVO。
class Widget {};
Widget makeWidget() {
Widget w;
return w;
}
Widget w = makeWidget();
条款21:利用重载技术(overload)避免隐式类型转换(implicit typr conversions)
class Rational {
public:
Rational(int numerator, int denominator = 1);
};
Rational r(1, 2);
r + 2; // 2 会被隐式转换成 Rational(2,1)
不如直接提供
Rational operator+(const Rational&, int);
Rational operator+(int, const Rational&);
条款22: 考虑以操作符复合形式(op=)取代其独身形式(op)
T& T::operator+=(const T& rhs)
{
// 核心逻辑
return *this;
} // operator+=没有临时对象的产生,直接复值给左值自变量
T operator+(const T& lhs, const T& rhs)
{
//核心逻辑
return 局部对象
} //有临时对象的产生
T operator+(T lhs, const T& rhs)
{
lhs += rhs;
return lhs;
} //通过前者实现后者
operator+=没有临时对象的产生,因故比operator+有更高的性能倾向。
条款23: 考虑使用其他程序库
iostream库(c++)类型安全,可扩充,stdio库(c)性能更高
条款24: 了解virtual functions、multiple inheritance、 virtual base classes、runtime type identification(RTTI)的成本
虚函数通过virtual tables (vtbl)与 virtual table pointer (vptr)实现,vbtl是一个虚函数指针构成的数组,在单一继承时,一个class只需一个vbtl和vptr就可以,在多重继承中,需要多个,每个基类对应一个。
凡是声明虚函数的对象,都有一个隐藏的数据成员vptr,用来指向vtbl的位置,vptr在构造函数执行过程中被逐步设置(派生类对象生成,先执行基类构造函数,此时vptr被设置成基类的,再执行剩下的派生类特有的构造函数部分,此时vptr被设置成派生类的)。
virtual base classes解决菱形继承中基类成员重复的问题
class A{...};
class B: virtual public A {};
class C: virtual public A {};
class D: public B, public C {};
其中A是virtual base class, B和C都采用虚拟继承,利用指针指向虚拟virtual base class成分,D对像的内存布局如下图:
上图如果考虑到vptrs,D对象的内存布局为
Rtti 即是runtime type identification让基类指针在运行时识别对象的真实类型机制,其类型也存储在vbtl中,
1. 虚函数为什么不能inline
Base* p = new Derived;
p->f(); // 虚调用
底层等价于 p->vptr->f(p); vptr在运行时通过对象的构造函数才能确定