Effective C++的读书简要笔记
Effective C++的读书简要笔记,在这里记录下,简要记录,需要更详细的可以去看书。
T1 cpp是语言联邦: 过程形式(procedure),面向对象(object-oriented),函数形式(functional),泛型形式(generic)和元编程(metaprogramming)
T2 尽量用const,enum, inline替换 #define 若使用define,记号名称可能从未被编译器看见过。
enum hack行为,如果不想让你pointer或者reference指向你的某个整数常量,enum可以实现约束,取一个enum的地址是非法的。
T3 尽可能使用const,const可以和函数返回值,各个参数,函数自身产生关联
T4 确定对象被使用前已先被初始化 , 初始化和赋值是有区别的,对类成员建议使用初始化列表,因为效率更高。
T5 了解C++默认编写创建了哪些函数,编译器会给class创建default构造函数,拷贝构造函数,拷贝赋值函数,析构函数。
T6 若是不使用编译器自动生成函数,应当明确拒绝。 使用私有函数或者 =delete
T7 为多态基类声明virtual析构函数。 防止删除base的时候对象的derived成分未被销毁。
T8 别让异常逃离析构函数。 (如果调用抛出异常,析构函数会传播异常) 简单方法1采用abort来强制结束程序,方法2采用try来吞下异常。 最好的做法是交给程序员来调用,在打算销魂前对该类进行一些关闭活动。
T9 绝不在析构和构造函数中调用virtual函数
C++构造机制是先构造基类再构造子类,(如果在基类对象调用虚函数,编译器会使virtual失效)
C++析构机制是先析构子类再析构基类,(同样在基类调用虚函数会失效)
T10 令operator=返回一个reference to *this
T11 在operator=中处理自我赋值, (Bitmap *tmp = _pd; _pd = new Bitmap(*rhs._pd); delete tmp; ) 这样能够避免new导致异常带来的影响
T12 复制对象时勿忘每一个成分。 在赋值时候,需要考虑到基类的赋值动作。
对于发现拷贝构造函数和赋值操作符有相似的代码,一个消除重复代码的操作时建议一个新的成员函数,如private 的_init() 来供两调用。
T13 用对象来管理资源。 依赖c++的析构函数来自动调用机制来确保资源被释放。 关键:1 获得资源后立刻放进管理对象内,2 管理对象运用析构函数确保资源被释放
T14 在资源管理类注意copying行为。 对RAII来说1 禁止复制,2 对底层资源使用引用计数法,
T15 在资源管理类中提供对资源的访问。 每个RAII class应提供一个显式转换的方法,安全的访问。
T16 成对使用new和delete时需要采取相同新式。 new 对应delete ,new [] 对应 delete []
T17 以独立语句将newed对象置入智能指针。 使用 std::smart_ptr ptr(new Object) 的方式,
T18 让接口容易被正确使用。 (在设计接口的时候应该考虑到用户会犯什么错)
T19 设计class犹如设计type。 如何构造析构,初始化和赋值的区别,pass-by-value如何实现,合法值,配合某个继承图系,转换关系,
T20 宁以pass-by-reference-to-const 替代 pass-by-pass。 pass-by-value都是以实参的副本为初值的,pass-by-reference-to-const不仅效率高,而且不存在对象切割的问题。
T21 必须返回对象时,不会返回其reference。 如果一个函数返回一个reference指向某个对象,隐藏其operator的风险,或者返回指针。应该返回一个新的对象。
有风险的是 {Vec a; return a; } 或者 {Vec *a = new Vec(); return a; } , 应该 { return Vec(); }
T22 将成员变量声明为private。 赋予客户访问数据的一致性。
T23 宁愿以non-member, non-friend替换member函数。 因为non-member函数提供的封装性质更好,不能访问private成员。
T24 若所有参数都需要类型转换,采用non-member函数。 non-member可以防止隐式转换而出错。
T25 考虑写出一个不抛出异常的swap函数。 可以采用特化的方式来造一个指定class的swap函数特化。
T26 尽可能延后变量定义式的出现时间。(容易,一般都会注意这个问题)
T27 尽量少做转型动作。 c++规则目标之一是保证类型错误绝不可能发生。如果必需,尝试将其隐藏在某个函数背后,随后调用该函数,而不需要使用转型。最后,使用新的转型而非旧式转型。
T28 避免返回handles指向对象内部成分。handlers包括reference,指针,迭代器等。防止降低对象封装性的风险。特别的 operator[] 允许直接提取vector中的元素,是例外的接口,特例。
T29 为异常安全而努力是值得的。若要进行一份较大工作量的操作,可以先建立一个副本,在副本上完成操作后,再swap回到原件。
T30 透彻了解inline的里里外外。 隐式申请inline的方式是直接将成员函数定义实现在class定义式内部;对virtual使用inline没有意义;对构造函数使用inline也会存在较大成本,编译器会根据自己的异常机制来拓展构造代码
T31 将文件间的编译依存关系降至最低。 可以分割较大类的class,一个分为两个,一个只提供接口,一个负责实现接口。关键在于以声明的依存性替代定义的依存性,那正是编译依存性最小化的本质。
T32 确定publish继承塑模出is-a的关系 根据实际情况来设计函数成员的位置,和是否出现。
T33 避免遮掩继承而来的名称。 继承类的函数搜索的顺序:继承类类内作用域 -> 继承类外围的作用域 -> 基类类内作用域 -> 基类外围作用域 -> 全局作用域
如果继承类的函数会覆盖同名的基类的函数(即使函数的参数不一,都会覆盖掉)(理由是:防止继承类附带地从疏远的基类中继承重载函数),
解决方案:在public继承下可以使用 using BasicClass::func1(); 在private继承下,using则失效,使用转交函数(forwarding function)的方式 class Derived { void func1() { Base::func1(); } }。 转交函数暗自转为inline。
T34 区分接口继承和实现继承。 publish继承区分接口继承和实现继承。 只继承接口则用纯虚继承,继承接口+实现但覆盖原有实现则使用虚拟继承,继承接口+实现且不覆盖原有实现则使用非虚拟继承。
T35 考虑virtual函数以外的其他选择 可以使用non-virtual来包装虚函数,也可以使用函数指针来实现,std::function 对象,相当于一个指向函数的泛化指针。
T36 绝不重新定义继承而来的non-virtual函数。 non-virtual函数是静态绑定的,对于指针会造成混乱。
T37 绝不重新定义继承而来的缺省参数值。 缺省参数值都是静态绑定的,如果基类和继承类的默认参数不一致,就算是虚函数,编译器也会调用基类的缺省值。危险。
T38 通过复合塑模出has-a关系或者 is-implemented-in-terms-of的关系。 区分有一个东西还是有一系列的东西,可以采用 std::list 作为成员的载体
T39 明智而审慎地使用private继承。 对于private继承非必要的情况可以采用复合(composition)
T40 明智而审慎地使用多重继承。 涉及到多重继承,调用函数最好指定调用的指定函数。 a.BaseClassA::print() 的方式来做。 同时还可以使用 virtual base class,所有直接继承自它的class采用virtual继承。
T41 了解隐式接口和编译期多态
在opp中最常用的是显式接口和运行期多态(runtime polymorphism)【哪个virtual函数被绑定】,在模板编程和泛型编程中更为强调隐式接口和编译期多态。【哪个重载函数被调用】
隐式接口不同于显示接口的函数签名式,而是由有效有效表达式匹配构成。 编译期多态通过template具现化和函数重载解析实现。
T42 了解typename的双重意义
在很多时候typename和class两个没啥区别,但是在有些时候必须得是typename。存在从属名称(template内出现的名称依赖于某个template参数),或者是嵌套从属名称(一个从属名称在class内呈现出嵌套状)
所以有一般型规则:当在template中指涉一个嵌套从属类型名称,必须在其前面增加关键字typename。如果不是则不能加上该关键字
T43 学习处理模板化基类内的名称
假如一个类A继承来自模板基类B,此时A也是一个模板类,
当编译器遭遇class template LoggingMsgSender定义式时,并不知道它继承的是怎么的base class,虽然很明显继承于MsgSender<Company>,但其中Company是个template参数,不到后来LoggingMsgSender被具现化是无法确切知道其是什么的,也就无法明确是否有个SendClear函数!
三种方法可以防止上面的情况来发生不能调用的问题。
(1) 在base class函数调用动作之前加上this指令,this->SendClear(info)
(2) 使用using声明式, Using MsgSender<Company>::SendClear;
(3) 明确指出被调用的函数位于base class内,(但要求方法不能有虚拟性),MsgSender<Company>::SendClear(info);
T44 将与参数无关的代码抽离template
template是节约时间和避免重复代码的方法,但可能会导致代码膨胀。解决方案是共性和变形分析(commonality and variablity analysis)。
如果对于两个参数的template的类A, template<typename T, size_t n>class Matrix; 里面的涉及不到n的参数的函数,void invert() 本来一份代码可以解决的话,现在有两份。
解决方案:分层: template<typename T>class MatrixBase;
然后 template<typename T, size_t n> Matrix: private MatrixBase<T>;
T45 运用成员函数模板接受所有兼容类型
指针可以很好地支持隐式转换,运行期多态的baseClass指针可以指向各种derivedClass。
这种构造方式是泛化(generalized)copy构造函数。构造函数并未被声明为explicit是蓄意的,因为原始指针之间的转化是隐式转化。
T46 需要类型转换时请为模板定义非成员函数
在template实参推导过程中,编译器是绝对不会把 隐式类型转换函数 纳入考虑范围之内的。
克服方式就是将函数定义为friend函数,friend声明式可以指涉某个特定函数。所以,class template就不依赖于template对函数operator*进行实参推导了。因为它已经是一个具体函数了。
在此还可以优化:让这个函数被自动具现化,需要将他声明在class内部,而class内部声明non-member函数的唯一方法是令它成为 friend
T47 请使用traits classes来表现类型信息
基于重载技术,traits可以在编译期对类型执行if-else的测试。 其用处在于实现if中的伪代码,并不是c++关键字或者一个预先定义好的构件,而是一种技术,一种共同遵守的协议。要求之一是对内置类和用户自定义类的表现一样好。