more effective c++笔记

24 阅读5分钟

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();

如果按“语法表面理解”,似乎会发生:

  1. 构造一个临时 Widget
  2. 拷贝到函数返回值临时对象
  3. 再拷贝到 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对像的内存布局如下图:

虚继承内存分布.png

上图如果考虑到vptrs,D对象的内存布局为

虚继承内存分布2.png

Rtti 即是runtime type identification让基类指针在运行时识别对象的真实类型机制,其类型也存储在vbtl中,

rtti.png

1. 虚函数为什么不能inline

Base* p = new Derived;
p->f();  // 虚调用

底层等价于 p->vptr->f(p); vptr在运行时通过对象的构造函数才能确定