Effective C++ 阅读笔记

effective c++ 读完有一种醍醐灌顶的感觉。之前使用C++编程时在许多结构以及用法上都模糊不清,对于不同的需求可能有多种不同的实践,但是并不了解什么才是最佳实践或者说是有没有最佳实践。Effective可以给你一些答案。


4.避免对象在初始化之前使用它
不同编译单元内的 non-local static对象 初始化先后顺序的处理
将该对象声明在为函数的 local static 对象

为内置类型对象进行初始化 在方法中的内置类型对象c++不保证初始化他们

5.了解c++默默编写调用了哪些函数

c++会在默认情况下生成 析构,复制构造 复制赋值函数
如果没有定义默认构造函数 则会自动生成构造函数
【默认生产的析构函数是非virtual 除非基类的析构函数是virtual的】

自动创建的赋值构造函数会把每一个non-static成员拷贝到目标对象

如果在一个含有reference成员的class内支持赋值操作 必须自行定义copy assignment
基类的copy assignment为private 则也无法自动生成copy assignment

6.阻止编译器自动生成函数
声明private 的复制构造与赋值构造函数 不定义,阻止使用赋值构造

不定义只生命会在link期报错而不是编译期,可以创建一个基类来实现防止拷贝构造
protected: 构造函数,析构函数
private:复制构造,赋值构造

7.对于那些不作为基类的class 声明virtual方法会增加内存大小 虚函数表vptr
std::string 不含有任何virtual函数 不能作为基类
c++ 没有sealed的禁止派生的机制

带有多态性质的base class 应该声明一个virtual析构函数
class如果不是为了作为基类使用,不该声明virtual析构

8.析构函数中的异常
std::abort() 强制结束
析构函数要捕获任何异常,swallow or abort
提供一个方法进行析构

9.不要在构造函数或析构函数中调用virtual
构造函数内的virtual函数不会调用derived 对象的实现
确定构造函数和析构函数内部不会调用任何热virtual函数

替代实现方式是创建一个非virtual方法,将参数传递到基类

10.operator= 返回一个reference to *this
int x,y,z;x=y=z=15
+= -= *= =

11.在operator中处理自我赋值

w& operator=(const w& rhs){
if(this == &rhs) return *this; //identity test
}

手动控制顺序

使用copy and swap 交换数据

copy assignment 声明为by value形式 然后直接使用swap

  1. 复制对象时不要忘记任何一个成员
    derived class 的复构造函数必须复制其base class的成分。调用base class 的cctor 在初始化列表中。

13.以对象管理资源

auto_ptr->unique_ptr 在对象释放的时候自动delete
RAII 资源取得的时机便是初始化时机

auto_ptr如果通过复制构造或者赋值构造 复制 自身会变成null

RCSP reference-counting smart pointer
shared_ptr

智能指针在析构时都是调用 delete 而不是delete []

14.资源管理中小心复制行为
抑制复制 使用引用计数

15.在资源管理类中提供对原始资源的访问
get() 方法暴露原始资源 或者使用隐式转换
隐式转换会出现一些其他的问题

16.使用new 和 delete时要采取相同的形式
最好不要对数组形式做typedef 难以正确delete []

17.以独立语句new 对象置入智能指针
new对象置入智能指针中是 要独立调用 不然可能会由于异常导致一些奇怪的情形

19.设计class犹如设计type
type的对象如何被创建与销毁
对象的初始化与赋值有什么样的差别
对象如果以值类型传递会如何
什么是type的合法值
type的继承关系是什么
新的type需要什么样的类型转换
什么样的操作符对新的type是合理的
什么样的标准函数需要驳回 private
那些成员时public 与private的
是否是需要定义成模板

  1. 用pass-by-reference-to-const 替换pass-by-value
    pass by value会造成 对象的切割 变为基类对象 如有
    内置类型使用值传递

21.在必须返回对象时不要返回其引用
在堆上或栈上创建对象都会导致问题 直接返回其值类型 会多一次复制构造函数 但是避免了一些其他的问题

22.将成员变量声明为private
将成员变量封装到getter setter中 便于修改实现
成员变量内容发生改变时的破坏量越小

protected 变量在修改时也可能需要对大量的derived 类进行修改
protected成员像public成员一样缺乏封装性

  1. prefer non-member non-friend 替换membr函数

把便利函数放在相同的namespace下面

24.若所有参数皆需要类型转换,请使用non-member函数

隐式转换的 operator重载,所有参数都要隐式转换时 不要定义在成员函数中

25.写一个不抛出异常的swap函数
pimpl模式 swap指针
默认的swap会创建一个temp对象

当std::swap效率不高是 提供一个swap成员函数,并确定该函数不抛出异常
提供一个member swap 也提供一个non-member 调用前者
using std::swap 然后调用swap

26.尽可能延后变量定义式的出现时间
定义变量 + 拷贝赋值 = 复制构造 减少构造函数的调用
变量在循环中定义 还是在循环外声明在循环内做赋值 那种效率更高?

27.尽量减少类型转换
(T)expr T(expr)
const_cast() 消除const
dynamic_cast() 向继承链上转换 可能耗费重大成本
reinterpret_cast() 低级转换 bitwise?
static_cast() 强迫隐士转换 non-const->const int->double void*->typed ptr
c++中一个对象可能有很多个布局地址,多重继承的每个基类带有一个offset

使用dynamic_cast 在一个base ptr上 操作derived class的方法,你认定他是指向derived instance

解决方式:在base type中定义一个virtual 方法

avoid cascading dynamic_cast ->use virtual function

28.避免返回handles指向对象内部成分
返回成员变量的指针或引用 加上const 防止被修改
即使加上const也是危险的 可以通过该handler的方法的返回值进行修改

29.exception-safe code
copy and swap 实现强异常安全
all or none

30.透彻了解inline
inline 是对编译器的申请,而不是强制
隐喻地提出inline,class 内部的成员函数
inline 和template通常都被定义与头文件中
function template不一定要inline

由于编译时进行inline 编译器需要知道方法具体的实现(vm 可以在运行时进行inline)
template 也需要别置于头文件 实现模板需要知道template的具体实现

virtual 方法不能inline 因为虚函数表依赖运行时进行调用
通过函数指针产生的调用也不会inline

inline函数无法进行升级 需要重新编译 non-inline只需要重新链接

31.将文件间的编译依存关系降低至最低
如果可以使用 obj ref 或obj ptr实现就不要使用obj
如果可以,尽量以class声明替换class 定义

handle classes and interface classes
full and declearation-only forms header

32.确保public 继承为 is-a的关系
public inheritance means the relation of ‘is-a’

33.避免遮掩继承而来的名称
derived class 内的名称会覆盖base class内的名称

34.区分接口继承和实现继承

可以给pure virtual 函数提供定义,调用的唯一途径是class::function
impure virtual 函数 derived class 可以override
impure virtual 会在子类未声明的情况下默默继承基类的实现,缺省实现有时候会造成问题
解决方案 声明一个non-virtual 方法 再声明一个pure virtual 默认实现时调用non-virtual方法

pure virtual 继承接口
impure virtual 继承接口和一份默认实现
non virtual 继承接口和一份强制实现

35.考虑virtual 函数外的其他选择

public non-virtual 成员函数间接调用private virtual 函数 non-virtual interface(NVI)
template method 设计模式。
NVI不一定是private方法 也可以是protected
NVI替代public virtual函数

基于function pointer实现strategy模式

36.绝不要重新定义继承而来的non-virtual 函数
viruta函数是动态绑定的 non-virtual是非动态绑定的
重新定义基类的non-virtual会导致一些不可意料的情况,取决于声明的指针类型

37.不要重新定义继承而来的缺省参数
virtual函数是动态绑定的 但是缺省参数是静态绑定的
最终调用derived class的方法时会使用base class 的参数
C++为了运行效率对缺省参数的绑定实现为静态绑定
使用NVI避免重新编写缺省参数

  1. 复合 is-implemented in terms of / has a

39.明智而谨慎地使用private 继承
private 继承 编译器不会自动将一个derived class对象转换为base class 对象
private继承而来的所有成员在derived class 中都会变成private

private意味着只有实现部分被继承,接口部分应略去。
private inherit means is-implemented-in-terms-of

尽量使用复合,必要时才使用private 继承

不存在is-a的关系的两个class,一个需要访问另一个的protected成员,或需要重新定义其一个或多个virtual函数,可以使用private集成
private 集成可以造成EBO empty base optimize
40.明智而审慎地使用多重继承