More Effective C++
这篇博客主要是用来加深自己对读过的书的记忆。写的内容可能只对我自己产生价值
Item 1: Distinguish between pointers and references
引用相较于指针
优势 他总是有效的,即没有null reference,指针则需要检查是否为空
劣势 指针可以指向一个新的对象,引用不行。指针可以使用nullptr表示不存在,如果你需要该变量拥有不存在的语义,使用pointer。
总结 当你确认你需要指向某个东西,并且绝对不会改变指向其它东西,使用reference,不然的话使用pointer
Item 2: Prefer C++-style casts
C的转型,无法区分想做的是什么类型的转型,而且较难分辨,尽量使用C++的新式转型
- static_cast 基本拥有C旧式转型的相同威力与意义
- cons_cast 用于强转const属性
- dynamic_cast 用于在继承体系中向下转型,转型失败时会以nullptr或者exception表现出来
- reinterpret_cast 用于转换二进制和序列化,或者函数指针的转型
Item 3: Never treat arrays polymorphically :skull:
数组类型不能被当作多态来进行传递,即
void printBSTArray(const BST array[]);
class BalancedBST: public BST {};
printBSTArray(BalancedBST) // error!
Why? 当你读取数组元素时,偏移是根据你申明的类型来进行计算的,但是子类的大小和父类基本都是不一致的,因此你实际使用的偏移是错误的,这是一个未定义行为!
Item 4: Avoid gratuitous default constructors
如果一个类不借助外部的信息就无法正确初始化,那么就应该避免提供默认构造函数,但这会带来以下几个问题
- 对于数组类型 A a[10] 没有默认构造函数即无法生成,需要使用别的方式生成,例如使用指针数组,而不是对象数组
- 对于一些基于模板的容器类型无法很好的兼容,因为他们可能假设你的类拥有默认构造函数
- 如果virtual base class 缺乏默认构造函数,后续继承他的类都需要知道其意义(bad design)。
结论,这是一个case by case的问题,根据实际情况进行抉择。
Item 5: Be wary of user-defined conversion functions
对于自己定义转换函数需要格外的小心,因为他们可能导致非预期的函数调用,编译器会想尽办法帮你编译成功,因此可能在你未预料的地方给你进行了隐饰转换,解决办法
- 定义 **asType()**的成员函数,进行显式的类型转换
- 使用explicit去除单自变量的constructor的隐式转换
Item 6: Distinguish between prefix and postfix forms of increment and decrement operators
前置++返回引用,后置++返回const 对象(const 对象防止 a++++)
后置++有一个临时变量的负担。
prefer prefix
Item 7: Never overload &&, ||, or ,
这些符号是由短路特性,而且保证从左往右计算,如果你对其进行重载,函数传进来的参数是无法保证计算顺序的,会导致与常规理解不符,从而导致未定义行为。
Item 8: Understand the different meanings of new and delete
new
- 分配内存
- 在该内存上调用构造函数
operator new (void* operator new(size_t size))
- 返回一块原始的未初始化的内存
placement new ( new (memory pointer) Type(args) )
- 在memory pointer上调用构造函数
new [] 和 operator new[] 对应的数组版
delete 与new对应,需要成对出现
delete - new
operator delete - operator new
Item 9: Use destructors to prevent resource leaks
因为有异常的存在,可能你释放资源之前就抛出了异常,导致资源泄漏。如果不断写catch会使代码乱七八糟,因此将资源释放放到析构函数中,即RAII
Item 10: Prevent resource leaks in constructors
如果contructor抛出异常,因为对象尚未完全构建完全,因此析构函数不会被调用,从而导致内存泄漏,解决办法为尽量使member不要是指针并且为智能指针。
Item 11: Prevent exceptions from leaving destructors
如果析构函数中抛出了异常有两个坏处1. 可能导致程序直接终止 2.导致析构函数需要执行的语句没有执行完,即内存泄漏,因此需要尽力避免析构函数抛出异常。
Item 12: Understand how throwing an exception differs from passing a parameter or calling a virtual function
- 异常类型永远会复制一份,无论捕获方式是什么
- 被抛出作为exception的对象,其被允许的类型转化方式比被传递到函数的去的方式少
- 异常比对是第一个成功就执行,而不是最佳匹配。
Item 13: Catch exceptions by reference
用指针捕获,容易导致传进来的指针已经失效,或者不知道该不该释放这个指针
用值捕获,需要多复制一份且不支持多态
用引用捕获,没有缺点!
Item 14: Use exception specifications judiciously
C++11基本不怎么使用了,仅用noexcept
Item 15: Understand the costs of exception handling
使用profile去检查性能的影响
Item 16: Remember the 80-20 rule
在真正关键的地方进行努力
Item 17: Consider using lazy evaluation
经典的计算机思想,仅在需要时计算。
Item 18: Amortize the cost of expected computations
将计算平坦到每一次调用中,例如你需要计算一个数组中的最大值,可以在每一次添加元素时,对最大值进行更新。
Item 19: Understand the origin of temporary objects
临时对象可能很耗成本,所以应该尽可能消除它们。例如reference to const 以及 value的地方就可能产生临时对象.
Item 20: Facilitate the return value optimization
详情看RVO
Item 21: Overload to avoid implicit type conversions
使用重载来消除隐式转换,从而消除临时变量,例如
const UPInt operator+(const UPInt& lhs, // add UPInt
const UPInt& rhs); // and UPInt
const UPInt operator+(const UPInt& lhs, // add UPInt
int rhs); // and int
const UPInt operator+(int lhs, // add int and
const UPInt& rhs); // UPInt
这样当执行 upi3 = upi1 + 10; 就不会有因为类型转换而产生临时变量。
Item 22: Consider using op= instead of stand-alone op
复合版本即+=,一般效率高于+,因为不需要产生临时变量。
Item 23: Consider alternative libraries
这个没啥说的,有什么高性能库就用什么吧。
Item 24: Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI
这个也没啥说的,只有实际碰到才能知道。
Item 25: Virtualizing constructors and non-member functions
虚构造函数,实际就是一个虚static成员函数,在构造函数中调用,从而实现虚构造函数
虚non-member函数,写一个虚函数做实际工作,再安排非虚函数对其进行调用。
Item 26: Limiting the number of objects of a class
设计一个Counted类,在内部进行计算,从而用户无感知
Item 27: Requiring or prohibiting heap-based objects
有一个hack的方式检查对象是否在heap中(利用程序的内存布局,但不具有可扩展性)
bool onHeap(const void *address)
{
char onTheStack; // local stack variable
return address < &onTheStack;
}
我们没有完美的方式来限制对象是否在heap中
Item 28: Smart pointers
C++11 已经支持了
Item 29: Reference counting
经典问题,不展开了
Item 30: Proxy classes
使用proxy对象来表示某些并不存在的对象,并且让用户无感知即为proxy classes
Item 31: Making functions virtual with respect to more than one object
multi dispatch,最佳解决手段,自己写虚表。
Item 32: Program in the future tense
时刻想着自己写的代码会被各种扩展,以及各种神奇的需求
Item 33: Make non-leaf classes abstract
专门抽象出Abstract类,让其他类来继承。
Item 34: Understand how to combine C++ and C in the same program
#ifdef __cplusplus
extern "C" {
#endif
void drawLine(int x1, int y1, int x2, int y2); // 以这种方式避免编译器重命名
void twiddleBits(unsigned char bits);
void simulate(int iterations);
...
#ifdef __cplusplus
}
#endif
If you want to mix C++ and C in the same program, remember the following simple guidelines:
■ Make sure the C++ and C compilers produce compatible object files.
■ Declare functions to be used by both languages extern “C”.
■ If at all possible, write main in C++.
■ Always use delete with memory from new; always use free with memory from malloc.
■ Limit what you pass between the two languages to data structures that compile under C; the C++ version of structs may contain nonvirtual member functions.
Item 35: Familiarize yourself with the language standard
熟悉语言标准!多看看RFC!