首页 > 编程 > C++ > 正文

Effective C++学习笔记

2019-11-06 06:44:02
字体:
来源:转载
供稿:网友

总结: 囫囵吞枣地看完了EffectiveC++这本书,收获不少。收获主要是以下几个方面:

编程注意细节 提高程序效率的细节比如constis-a 和 has-a的区别pure-virtual、impure-virtual和non-virtual之间继承该如何处理继承中隐藏基类的成员等设计方法 什么时候该继承(is-a),什么时候该以对象作为data member(is-inplemented-in-terms-of);Template method、 Strategy, 抽象工厂等设计模式泛型编程与模板编程异常安全性 异常处理copy-swap技术

由于没有太多的实战经历,所以有些东西并不是很懂。 所幸在看书过程中,做过一些笔记整理,其中 大部分内容摘抄自书上,一小部分加上自己的一点点理解,方便日后回顾。

02: 对于单纯常量,最好以const对象或 enums 替换#define

03: 尽可能使用const

mutable 关键字表征 const成员函数可以修改mutable成员

04: 为内置对象进行手工初始化,C++并不保证初始化他们;

05: 在类中,如果有引用 或const对象,记得要自己编写构造函数,拷贝构造函数等,因为编译器自己生成的相关函数很大可能达不到要求。

06: 为了驳回编译器自动提供的功能(构造函数,拷贝等),可以将相应的member function declared PRivate,并且不予实现; 或者使用像Uncopyable这样的base class;

07: 当类作为基类的时候,有可能表现出多态的时候,应将虚构函数设计成virtual

09: 绝不在构造函数和析构函数中调用virtual function; 如果在base class 调用了virtual function,在派生类的构造函数初始化基类的时候,实际上还是调用的是base class的function,并没有调用dervied class的内容(此时derived class 并没有完成初始化)

10: 令 Operator= 返回一个referece to *this (方便连续赋值 如 a = b = c;);

11: 在operator=中处理“自我赋值”copy and swap 技术实现自我赋值;

class Widgt {void swap(Widgt& rhs);// 交换*this 和 rhs的数据}Widget& Widget::operator=(const Widget& rhs) { Widget tmp(rhs); swap(rhs); return *this;}

12: 复制对象的时候务必记住每一个成员 copying函数应该确保复制“对象内的所有成员变量” 以及“所有base Class 的成分”

13: 以对象管理资源 记得用智能指针管理资源;

14: 在资源管理类中心小心copying行为

当资源管理类需要copy的时候 需要从以下几个方面考虑 禁止复制(将拷贝构造函数,以及拷贝赋值函数 设置于private有点类似于unique_ptr<>)对底层资源使用“引用计数法” 模仿“shared_ptr”复制底部资源(深拷贝,每一份对象都有自己的heap内存)转移资源的管理权(有点像 auto_ptr )

15: 资源管理类中需要提供对原始资源的访问

智能指针类可以隐式转化为底部的原始指针,便于访问底部的成员可以有显式转换(较为安全)和隐式转换(对客户比较安全)

16: 成对使用new 和delete 尤其注意在利用typedef中声明新的类型名时(尽量少用typedef声明数组类型)

17: 以独立语句将new出来的对象置入智能指针

Design and Declarations

18: make interfaces easy to use correctly and hard to use incorrectly 令自己的types和内置types的行为一致 尽量保持接口一致性 如STL容器的接口 19: Treat class design as type design type的对象应该如何该创建和销毁;对象的初始化和赋值该有什么样的区别新type的对象如果pass by value 意味着什么新type的合法值 ?哪些赋值是合法的是否需要配合某个继承图系(inheritance graph)吗?什么样的类型转换?什么样的操作符和函数对此新type而言是合理的?(设计接口)什么样的标准函数应该被驳回?(设计为private)谁该取用新type的成员? (设计为public)20: 以pass-by-reference to const 替换pass-by-value 前者比较高效,同时可以避免切割问题;对于内置类型、STL的迭代器和函数对象。 对他们而言,pass-by-value往往比较合适21: Don’t try to return a reference when you must return an object; 绝对不要返回pointer或reference指向一个local stack对象(会产生未定义的行为)绝对不要返回指向一个heap-allocated对象(无法控制对象的销毁)绝对不要返回pointer或reference指向一个local static对象(可能需要多个这样的对象)(在多线程情况下也难以保证安全)22: Declare data members private 可以实现对data member的各种权限控制有利于封装,注意protected 并不比public更具有封装性23: Prefer non-member non-friend替换member函数 其实是具有更大的封装性(注意namespace)增加封装性,packaging flexibility 和机能扩充性24: 若所有参数需要类型转换,请将此函数设计成non-member函数 有理数类(Rational)的的四则运算; Rational tmp = Rational() * 2 ; Rational tmp = 2 * Rational(); 考虑这两种情况

25 : Consider support for a non-throwing swap( 不抛异常)

缺省版本swap

如果缺省版本的效率不够,可以尝试做

提供一个public swap函数(这个函数,不要抛出异常)在class 或template所在的命名空间内提供一个non-member swap,并调用上述的swap成员函数如果正编写一个class(非template),需要特化std::swap, 并令他调用你的成员swap

注意: 不要忘std里加入任何新的东西

// default swaptemplate <typename T>void swap(T& a, T& b){ T temp(a); a = b; b = tmp;}class Widget { public: void swap(Widget &other);}26: 尽可能延后变量定义式的出现的时间(用到的时候才再去声明) 循环里用到的变量尽量在循环里声明,除非 赋值成本比构造加析构低,处理代码中效率高度敏感的部分

27: 尽量少做转型动作(如果转型必要,就封装在函数里面)(新式转型 好于 旧式转型)

const_castdynamic_caststatic_cast reinterpret_cast 不适合移植

28: Avoid returning “handles”to object internals

handles (pointer, reference, iterators)

29: Strive for exception safety code(为异常安全性努力是值得的)

带有异常安全性的函数会: 不泄露任何资源不允许数据败坏

异常安全函数(Exception-safe code)提供下面三个保证之一

基本承诺

程序内的任何事物都保持在有效状态下(对象或者数据结构不会有因此而损坏)当异常时,可以选用默认状态,也可以回复到变换之前的状态

强烈保证(往往以copy-and-swap实现出来)

函数成功,就完全成功,如果失败,程序会回复到调用函数之前的状态nothrow 保证

30: Understand the ins and outs of inlining

virtual 和inline同时出现,编译器往往会拒绝inlineinline限制在小型、频繁调用的函数身上构造函数最好不要inline

31: 将文件间的编译依存关系降至最低

接口与实现的分离 如果使用object reference 或者 object pointers可以完成任务,就不要使用objects如果能够,尽量以class声明式替换定义式为声明式和定义式提供不同的头文件

32: 确定public继承是is-a的关系(注意区分has-a)

适用于derived class身上的所有事情都能适用于base class身上

33: Avoid hiding inherited names(避免隐藏继承的成员名)

避免隐藏基类的成员名 可以使用using 声明式或者 forwarding function

34: Differentiate between inheritance of interface and inheritance of implementation(区分接口继承和实现继承)

pure virtual: 为了让derived class继承函数接口(继承了的具象类都要重新声明)impure virtual: 继承该函数的接口和缺省实现non-virtual:为了令derived classes继承函数的接口及一份强制性实现

35: 考虑virtual函数以外的其他设计方式(提及设计模式中的Template Method 和 Strategy)

令客户通过public non-virtual成员函数间接调用private函数(NVI手法)/// Template Methodclass GameCharacter {public: int healthValue() const { ··· // 做一些事前工作 int retVal = doHealthValue(); ··· //做一些事后工作 return value; } ···private: virtual int doHealthValue() const { ··· // 缺省算法,计算健康指数 } }借由function pointers实现strategy模式class GameCharacter;int defaultHealthCalc(const GameCharacter& gc);class GameCharacter {public: typedef std::function<int (const GameCharacter&)> HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {} int healthValue() const { return healthFunc(*this);}private: HealthCalcFunc healthFunc;};/// 可以转换成function对象short calcHealth(const GameCharacter&); // 返回类型 non-intstruct HealthCalculator { //为计算健康值设计的函数对象 int operator()(const GameCharacter&) const {···}};class GameLevel {public: float health(const GameCharacter&) const; // 成员函数 ···};使用non-virtual interface手法,那是Template Method设计模式的一种特殊形式,它以public non-virtual成员函数包裹性较低访问性(private或protected)的virtual函数将virtual函数替换为“函数指针成员变量”,这是strategy设计模式的一种分解表现形式以function成员变量替换virtual函数, 因此允许使用任何可以转变成(callable entity)function对象的表达式,也是strategy的一种表现形式

36: Never redefine an inherited non-virtual function.

如果重新定义从基类继承的non-virtual function, 那么就应该是设计出现了问题

37: 绝不重新定义继承而来的缺省参数值(针对virtual函数)

virtual函数系动态绑定,而缺省参数值却是静态绑定如果需要缺省值,应该将该函数设计为non-virtual函数,再设计一个private的virtual function 如同前面介绍的Template Method设计

38: Model “has-a”or “is-implementated-in-terms-of” through composition

在应用域内,是has-a的关系在实现域中,是根据某物实现出 例如STL库中的stack是以vector为底层数据结构实现的

39: 明智而谨慎地使用private继承

private继承某种意义上说 is-implemented-in-terms-of, 所以尽可能使用38条中使用复合的方式实现除非确定private继承可以造成empty base最优化

40: 明智而谨慎地使用多重继承

多重继承比单一继承复杂;以及可能会对virtual继承的需要virtual继承会增加大小,速度,初始化复杂度等成本

模板与泛型编程

41: 了解隐式接口和编译器多态

以不同的template参数具现化function templates会导致调用不同的函数,这就是编译期多态(有点像重载函数) classes 和 templates都支持接口(interface)和多态(polymorphism)对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期对template参数而言,接口是隐式(implicit)的,奠基于有效表达式;多态则是通过template具现化发生于编译期

42: 了解typename的双重意义

声明template参数时,class和typename可以互换记得使用typename标识嵌套从属类型名称;但不要在base class List或 member initialization list内以它作为base class的修饰符

43: 学习处理模板化基类内的名称

继承模板基类的时候,基类的成员函数不可见;这个时候可以用一下三种方式解决 在前面添加this指针利用using语句,使模板基类的函数可见利用基类的类名加::调用基类函数

44: 将与参数无关的代码抽离template

template生成多个classes和多个function,所以任何template代码都不该与某个造成膨胀(怎么造成代码膨胀呢?)的template参数产生相依关系因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数因类型参数(type parameters)而造成的代码膨胀,往往可以降低,做法是让带有王权相同的二进制表述的instantiation types共享实现码

45: 运用成员函数模板接受所有的兼容类型

请使用member function templates生成“可接受所有兼容类型”的函数如果你声明member templates 用于“泛化copy构造”或泛化assignment操作,还是需要声明正常的拷贝构造函数和copy assignment操作符46: 需要类型转换时请为模板定义非成员函数 编写class template,需要支持与此template相关函数 支持参数之隐式类型转换 时,请将那些函数定义为 class template内部的friend函数

47: 请使用traits classes表现类型信息

Trait classes使得“类型相关信息”在编译器可以用,以template特化实现整合overloading技术

48: 认识template元编程

49: 了解new-handler的行为

new-handler函数要完成以下的事情:

让更多内存可被使用安装另一个new-handler(令new-handler修改会影响new-handler行为的static数据,global数据)卸除new-handler(将null指针传递给set_new_handler)跑出bad_alloc(或派生出来的)异常

Nothrow new是一个颇为局限的工具,因为它只适用于内存分配 够来的构造函数还是可能跑出异常

50: 了解new和delete的合理替换时机

51: 编写new和delete时需要固守常规

52: 写了placement new也要写placement delete(不熟悉)

53: pay attention to compiler warnings

编译器的警告能力是不一样的; 编写程序,争取完全无警告

54: 让自己熟悉TR1在内的标准程序库(现在C++11基本都实现了)

55: 让自己熟悉boost程序库(泛型编程, 模板元编程)

没有理解和不太懂的地方,也有几处,主要集中于:

泛型编程模板元编程trait type、trait class, 特化以及偏特化

接下来的学习之路也需要偏重以下上述方面。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选