Effective C++ 读书笔记 (改善程序与设计的具体做法)
# 宁愿以编译器替换预处理器(尽量以const,enum,inline(替换形似函数的宏#define) 替换 #define)
对于单纯常量,以const对象 或者enum 替换 #define
#define APSECT_RATIO 1.653
//以上记号名称 APSECT_RATIO 也许在编译器开始处理源码之前它就被预处理器移走了,于是APSECT_RATIO可能未进入记号表(symbol table)内,于是当你运用此常量时发生编译错误,编译信息可能只会提到1.653而不是APSECT_RATIO,你将需要花很多时间去追踪它在那个头文件。
#尽可能地使用const
#当const 和 non-const 成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复
non-const成员函数本来就可以对其对象做任何动作,所以在其中调用一个const成员函数并不会带来风险)
#如果成员变量是 const 或者 reference,则一定需要初值,不可赋值
#为内置类型对象进行手工初始化 (需要注意赋值(assignments)与 初始化(initialization)的区别)
class ABEntry{ public: ABEntry(const std::string& name,const std::string& address); private: std::string TheName; std::string The Address; }; 赋值: //先调用default constructor 再调用 copy assignment operater ABEntry::ABEntry(const std::string& name,const std::string& address){ TheName = name; The Address = address; } 成员初值列表初始化: //只调用一次copy constructor,且class成员变量以其声明次序被初始化 ABEntry::ABEntry(const std::string& name,const std::string& address) :TheName(name),TheAddress(address) { }
#为免除“跨编译单元编译初始化次序问题”,以local static 对象替换 non-local static对象,并返回此对象的引用,因为C++保证:local static对象保证在被第一次使用时会被初始化
跨编译单元编译初始化次序问题:某编译单元内的某个 non-local static 的初始化动作需要使用另外一个编译单元的non-local static 对象,而需要用的这个对象可能还未被初始化。
static 对象指存储区不属于stack,heap-based 且“寿命”从构造至程序结束的对象(global对象,class内,函数内被声明为static的对象)
在函数内的 static 称为local static (因为对函数而言,它们是local),其他称为non-local static。
local static 对象在其所属函数被调用之前该对象都不存在,只有在第一次调用,local static 对象才被构造,而non-local static对象从mian( )开始就构造,在main( )结束被析构。
**关于 构造/析构/赋值运算
#如果你打算在一个内含 reference 成员的 class 内支持赋值操作(assignment),你必须要定义自己的 copy assignment
(编译器自动生成的那一个copy assignment 会拒绝编译这样的赋值操作)
template<class T> class NameObject{ public: NameObject(std::string& name , const T& value); private: std::string& nameValue; //这是一个reference const T objectValue; //这是一个const } NameObject<int> p1("Reven",12); NameObject<int> p2("Lux",21); p1 = p2 ; //这行不通!记住 :不允许 reference 指向不同的对象! #为驳回编译器自动(暗自)提供的机能(构造函数,赋值运算符,析构...),可以把相应的成员函数声明为 private 并且不给予实现 class A {....} A object1; A object2; A object3(object1); object1 = object2; //为了阻止上面的行为 我们可以这样做: class A{ private: A(const A); //只声明,不给予实现 A& operator=(const A&); }
#polymorphic base classes 应该声明一个 virtual destructor ,class带有任何 virtual function那么它就应该拥有一个 virtual destructor. #若classes 设计目的不是作为 base class 使用则不应该声明 virtual destructor
原因:当 derived class 对象经由一个 base class 指针删除,而该 base class 带着的是 non-virtual destructor,其结果将是未定义的----实际执行时发生的是 对象的 derived 成分没有被销毁(只局部销毁对象)!
想要实现 virtual 函数,对象需要携带某些信息(vptr virtual table pointer,class 对象体积也会变大一般多一个指针大小 )用来在运行期决定哪一个 virtual 函数应该被调用。
vptr 指向一个由 函数指针 构成的数组,称为vtbl(virtual table),每个带有 virtual 函数的 class 都有一个相应的 vtbl .
请求函数调用 -> vptr -> vtbl -> 函数调用
析构函数运作方式:最深层派生类的析构函数最先被调用(构造函数反之)
#析构函数绝对不要吐出异常,要么吞下要么结束程序(防止异常传播),如果必须要处理异常,提供一个普通函数执行该操作
#绝对不要在构造和析构过程中调用 virtual 函数(通俗一点说virtual函数在构造函数时期还不是virtual函数!)
class Transaction{ public : Transaction( ){ logTransaction( ) }; virtural void logTransaction( ) const = 0 ; ... // pure virtual function } class BuyTransaction : public Transaction{ public: virtual void logTransaction( ) const ; ... } BuyTransction object ; //先执行Transaction( ),而这时候会执行 Transaction::logTransaction( ) ! 不是 BuyTransaction::logTransaction( )! //另外一个观点理由:要知道derived class的函数几乎必然取用 local 成员变量,而那些成员还尚未被初始化!
#关于 operator=,请把 assignment操作符 返回一个 reference to *this
Widget& operator = (const Widget& rhs){ .. return* this; //返回左侧对象 }
#关于assignment操作符 处理自我赋值的情况
class Bitmap{...}; class Widget{ .... private: Bitmap* pb; }; 方法一 Widget& Widget::operator=(const Widget& rhs){ if(this = &rhs) return *this; //证同测试,若是自我赋值则什么也不做 delete pb; pb = new Bitmap(*rhs.pb); return *this; } 方法二 Widget& operator = (const Widget& rhs){ Widget temp(rhs); //制作副本 swap(temp); //将*this数据和复件数据交换 return *this; }
#Copying函数应该确保复制“对象内的所有成员变量”以及“所有 base class 成分(可以通过调用base class 内适当的copying函数)”
如果你自己手写了 copying函数,则在你为 class添加成员变量 或者 发生继承 时你也必须要同时修改 copying函数
###关于资源管理
#防止资源泄露使用RAII(resource acquisition is initialization 获得资源后立刻放进管理对象)对象,建议使用tr1::shared_ptr , auto::ptr 的复制动作会使被复制物指向null(防止对象被删除一次以上导致未定义行为!),而shared_只会增加引用计数,在无人指向它时自动删除资源
#复制ARII对象必须一并复制它所管理的资源(深拷贝),所以资源的copying行为决定RALL对象的copying行为
(shared_ptr 允许指定 删除器 ,这是一个函数或者函数对象,当引用次数为0时便被调用)
#APIs往往要求访问原始资源(raw resource),所以每一个 RAII class 应该提供访问原始资源的办法
(shared_ptr 和 auto_ptr 提供get( )成员函数执行显示转换来返回智能指针内部的原始指针(复件))
#new 和 delete 要采取相同形式成对使用(new -- delete new[ ] -- delete[ ])
单一对象 object
对象数组 n object object object ...(n 是数组的大小)
#以独立语句将 newed 对象置入智能指针
int priority( ); void processWidget(std::tr1::shared_ptr<Widget> pw, int priority); 考虑调用 processWidget: processWidget(std::tr1::shared_ptr<Widget>(new Widget) , priority( ) )//泄露风险! 正确写法:std::tr1::shared_ptr<Widget> pw (new Widget) //先创建Widget 放入智能指针!因为priority( )调用可能会异常! processWidget( pw , priority( ) )
###设计与声明
#尽量让你的 types 的行为与内置行为一致(因为客户知道内置类型有些什么行为,这样可以避免客户犯错)
#tr1::shared_ptr 支持定制删除器(custom deleter),tr1::shared_ptr可以防止对象在动态连接程序库(DDL)中被 new 创建 却在另外一个 动态链接库内被 delete销毁“, tr1::shared_ptr 还可以用来自动解除互斥锁(mutexes)
#尽量以 pass-by-reference-const 替换 pass-by-value,前者相对高效而且可以避免切割问题(slicing),
但内置类型更适合pass-by-value(例如 iterator,函数对象)
class Person{ public: Person(); virtual ~Person(); private: std::string name; }; class Student:public Person{ public: Student(); virtual ~Student(); private: std::string schoolName; }; bool funcStudent(Student s) //函数以by-value方式接受学生 Student plato; //Person()->Student() bool palotoIsOK = funcStudent(plato) //copyingctorPerson()->copyingctorStudent()->copyingctor(string)->copyingctor(string) pass by reference-to-const: bool funcStudent(const Student& s) //没有任何构造函数或者析构函数被调用,因为没有新对象被创建 避免切割问题:当derived class 对象 以by value方式传递 并被视为一个base class 对象,会调用 base class copyingctor ,对象被切割只剩下base class class Window{ public: std::string name( ) const; virtual void display( )const ; }; class Window1 :public Window{ public:virtual dispaly( )const; }; void printNameDisplay(Window w){ //错误!w类型可以是Window可以是Window1!参数可能被切割! //应该改写成void printNameDisplay(Window &w) std::cout<<w.name; w.display(); }
#不要返回 pointer 或者 reference 指向一个 local stack对象,也不要返回 reference 指向一个 heap-allocated 对象
local对象在函数推出之前就会被销毁,返回其reference会导致未定义行为!
对于 heap-allocated 对象,你返回其引用,面临的问题是:以后该如何把这个 new 出来的对象实施 delete?
#将成员变量声明为 private(赋予客户访问数据一致性,可细微划分控制),且 protected 并不比 public 更具有封装性!
封装隐藏成员变量只有成员函数可以影响它们,并且日后更改时不会破坏太多的客户代码!
public成员变量会破坏使用它的所有代码,protected成员变量会破坏所有使用它的 derived class!
#宁可拿 non-member non-friend函数 替换member函数,有利于增加封装性
因为 non-member non-friend函数 不可以访问 class 内的 private 成员
#swap( )对的类型效率不高时,可以自己写一个swap( )成员函数,并确定这个函数不抛出异常
#如果你提供了一个member swap,也提供一个non-menber swap用来调用前者,对于class,请特化 std::swap
#调用swap( ) 应该针对 std::swap 使用 using 声明式
pointer to implementation(piml 设计手法)
class WidgetImpl{ public: ... //里面的数据细节不重要 private: ... } class Widget{ public: Widget(const Widget& rhs); //copyingctor Widget& operator=(const Widget& rhs){ ... *pImpl = *(rhs.pImpl); ... } void swap(Widget& other){ using std::swap; swap(this.pImpl,other.pImpl) } private: WidgetImpl* pImpl; } //用以下函数调用swap( )成员函数即可突破访问private的权限 namespace std{ template<> //全特化 void swap<Widget>(Widget& rhs,Widget& lhs){ rhs.swap(lhs.pImpl); //只置换指针 效率很高 } }

浙公网安备 33010602011771号