【Effective C++】

Effective C++:pdf下载链接:
链接:https://pan.baidu.com/s/1jfg-UJTzQHPfYaFrXT38lw?pwd=1234
提取码:1234

条款01: View C++ as a federation of languages.
条款02: Prefer const, enums and inlines to #defines.
  • #define 是在预处理阶段被直接做文本替换的,所以对于编译器是不可见的。 用常量替换 #define,注意类常量要加 static 以保证唯一性。
  • #define 误用宏。例如:
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
int a = 5, b =10;
CALL_WITH_MAX(++a, b); //a累加两次
CALL_WITH_MAX(++a, b + 10); //a累加一次
// 改用:
template<typename T>
inline void callWithMax(const T&a, const T&b){
	f(a > b ? a : b);
}
条款03: Use const whenever possible.
  • 最好的用法是面对函数声明时的应用。
  • const 成员函数:注意两个成员函数如果只是常量性不同,是可以被重载的
  • 编译器强制实行btiwise constness,自己编写的程序使用concept constness
  • 可以使用non-const 调用 const 版本避免代码重复,反过来是不行的
calss TextBlock{
const char& operator[](std::size_t position const){
...
return text[position];}
char& operator[](std::size_t position){
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);}}
条款04: Make sure that objects are initialzied before they’re used.
  • C++ 规定,对象的成员变量的初始化发生在进入构造函数本体之前。所以需要使用成员列表初始化(memeber initialization list)进行成员变量初始化来替代赋值操作。
  • 对内置型对象进行手工初始化,C++不保证初始化。
  • 免除“跨编译单元之初始化次序” 的问题,用 local static 对象替换non-local 对象。
条款05: Know what functions C++ silently write and calls.
  • 编译器会暗自给class创建default 构造函数,copy 构造函数,copy assignment 操作符,以及析构函数。
条款06: Explicitly disallow the use of complier-generated functions you do not want.
  • 为了驳回编译器自动提供的函数,可以将对应的成员函数声明为private 并且不予以实现。或者使用继承的方式。
class Uncopyable(){
protected:
	Uncopyable(){}
	~Uncopyable(){}
private:
	Uncopyable(const Uncopyable&);
	Uncopyable& operator=(const Uncopyable&);};
//为了阻止Home对象被拷贝
class Home: private Uncopyable{
}
条款07: Declare destructors virtual in polymorphic base classes.
  • polymorphic base classes 应该声明一个virtual 析构函数,如果class 带有任何的virtual 函数,就应该声明一个virtual 析构函数。
  • calsses 的设计目的如果不是作为一个base calsses 使用,就 不应该声明virtual析构函数。
条款08: Prevent exceptions from leaving destructors.
  • 析构函数绝对不要吐出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吐下他们或者结束进程。
  • 如果要对某个操作函数运行期间抛出的异常做出反应,就应该提供一个普通函数执行该操作。(一方面,用户可以执行close函数来关闭,同时析构函数做了双重保险)
class DBConn{
public:
	void close(){
		db.close();
		closed = true;
	}
	~DBConn(){
		if(!closed){
			try{ db.close();}
			catch(){制作操作记录,记录失败;}
		}
	}
private:
	DBConnection db;
	bool closed;
};
条款09: Never call virtual functions during construction or destruction.
  • 因为这类调用从不下降至derived class(比起当前执行的构造函数和析构函数那一层)
条款10: Have assignment operators return a reference to *this.
  • 为了实现连锁赋值 “x = (y = (z = 15));”, 应该返回一个reference to this.
BaseClass& operator=(const BaseClass& rhs){
	...
	return *this;
} 
条款11: Handle assignment to self in operatro=.
  • 为了避免“在停止使用资源以前就意外释放了它 ”的陷阱,传统做法是增加一个 “证同测试”。
  • 为了避免 new 失败导致的异常和指针指向空。可以利用“ operator = ” 的异常安全性。
  • 确保当对象自我赋值时有良好的行为,可以使用 “证同测试”, 精心周到的语句,以及copy and swap 技术。
条款12: Copy all parts of an object.
  • 当编写一个copying 函数时,确保复制所有的 lcoal 成员变量,调用所有的 base classes 中的适当的 copying 函数。
  • 用一个copying 函数去调用另一个copying 函数时不合理的。
条款13: Use objects to manage resources.
  • 为防止资源泄露,使用RAII对象(Resource Acquisition Is Initialization),他们在构造函数中获得资源,并在析构函数中释放资源。
条款14: Think carefully about copying behavior in resource-managing classes.
  • 在资源管理类中,当一个RAII 对象被复制时: 禁止复制(copying 私有化);对底层资源进行“引用计数”; 复制底部资源;转移底部资源所有权;
条款15: Provide access to raw resources in resource-managing classes.
  • 提供一个显示的转换函数(如get成员函数)将RAII class 转换成其底部资源,或者是提供隐式转化,答案主要取决于其设计的特定工作。
  • APIs 往往会要求访问原始资源(raw resources),所以应该踢狗一个“取得底部资源的方法”。
条款16: Use the same form in corresponding uses of new and delete.
  • 如果在new 中使用了[],必须在delete 中也使用 [] 。
条款17: Store newed objectes in smart pointers in standalone statements.
int pri();
void process(std::tr1::shared_ptr<widget> pw, int pri);
//调用:
process(new widget, pri());//error, share_ptr 不支持隐士转换;
process(std::tr1::shared_ptr<widget>(new widget), pri);
//对于c++ 其执行顺序不同可能是:
//调用new widget, pri(), 调用tr1::shared_ptr构造函数;但是如果pri()异常,就会导致资源泄露
  • 以独立的语句将newed对象存储在智能指针内,如果不这样做,一旦异常抛出,有可能导致难以察觉的资源泄露。
条款18: Make interfaces easy to use correctly and hard to use incorrectly.
  • 促进正确使用的方法包括接口一致性,内置类型的行为兼容。
  • 组织误用,包括新建累心,限制操作,束缚对象值,消除客户资源管理的责任。
  • 可以为shared_ptr 指定删除器,而且会自动使用“每个指针专属的删除器”。来消除DLL 问题。
条款19: Treat class design as type design.
  • 如何创建和销毁?
  • 对象初始化和对象赋值有什么差别?
  • 对于如果被passed by value 意味什么?
  • 什么是新的 “合法值”?
  • 需要配合某个继承图系吗?
  • 需要什么样的转换?
  • 什么样的操作符和函数是合理的?
  • 谁该取用新的成员?
  • 什么是新的 “未声明的接口“?
  • 新的type 有什么一般化?或许需要一个class template。
  • 确定需要一个新type吗?
条款20: Prefer pass-by-reference-to-const to pass-by-value.
  • 可以解决 切割问题 (slicing):
class window(){
public:
std::string name() const;
virtual void display() const;};
class windowwithS: public window{
public:
virtual void display() const;};
//
void printName(window w){
w.display();}
// 如果是windowwithS 类被调用;那么调用的display 函数是父类的:
  • references 往往采用指针的方式来实现的,所以如果是对象属于内置类型,可能by value往往比reference 效率更高。
条款21: Don’t try to return a reference when you must return an object.
  • 绝对不要返回 pointer or reference 指向一个 local stack 对象,或者返回 reference 指向一个 heap-allocated 对象,或者返回 pointer or reference 指向一个local static 对象而有可能同时需要多个这样的对象。
条款22: Declare data members private.
  • 切记将成员变量声明为private, 这可赋予客户访问数据的一致性、可细微划分访问控制,允诺约束条件获得保证,并给class 作者以充分的弹性。
  • protected 并不比public 更具有封装性。
条款23: Prefer non-member non-friend functions to member functions.
  • 应该使用 non-member non-friend 函数来替代member 函数, 这样可以增加封装性和包裹弹性(packing flexibility)和机能扩充性。
条款24: Declare non-member functions when type conversions should apply to all parameters.
  • 令classes 支持隐式类型转换通常是糟糕的注意。但对于对于数值类型时,似乎是合理的。
class rational{
public:
	rational(int numerator = 0, int denominator = 1);
	int numerator() const; 
	int denominator() const;
	const rational operator* (const rational& rhs) const;
};
// .......
result = oneHalf * 2; // 成功
result = 2 * oneHalf; // 编译失败
// 只有当参数列于参数列内,这个参数才是隐式类型转换的合格参与者。
// 为了实现交换律实现一个 non-member 函数
const rational operator*(const rational& lhs, const rational& rhs){
......
}
  • 如果你需要为某个函数的所有参数进行类型,这个函数必须是一个non-member 的。
条款25: Consider support for a non-throwing swap.

swap 函数的一般版本是低效的,要设计针对不同类的不同特化swap 函数;

namespace std{
	template<> // 表示std::swap 的一个全特化版本;
	void swap<widget>(widget& a, widget& b){
		swap(a.pointer, b.pointer);
	}
};

通常我们不允许改变std 命名空间里的任何东西,但是可以为标准的templates 制造特化版本。同时,无法直接访问对象的pointer ,所有可以将这个swap 声明为一个friend 函数。

class widget{
public: 
	void swap(widget& other){
		using std::swap;
		swap(pointer, other.pointer);
	}
}
namespace std{
	template<> // 表示std::swap 的一个全特化版本;
	void swap<widget>(widget& a, widget& b){
		a.swap(b);
	}
};

这种方法是可以的,但是存在一个问题,就是如果widget 是hi一个class templates,那么会在特化std:: swap 时遇上乱流。

template<typename T>
class widget{...};
//
class std{
template<typename T>
void swap<widget<T>>(widget<T>&a, widget<T>&b){
a.swap(b);}// error,不合法
};
  • C++ 只允许对class templates 偏特化,但是在function templates 身上偏特化是行不通的。这段代码是通不过编译的。一般来说,重载function templates 是没有问题的,但是std 是一个比较特殊的命名空间,可以全特化std 中的templates, 但是不可以添加新的templates。因此,最终还是生命一个non-member swap 来让它调用member swap,但是不再将那个non-member swap 声明为std::swap 的特化版本。
  1. 提供一个public swap 成员函数,让它高效的置换你的类型的两个对象值。
  2. 在你的class 或者template 所在的命名空间提供一个non-member swap,并令他调用上述的swap 成员函数。
  3. 如果你正在编写一个class,为你的class 特化std::swap。并且调用你的swap 成员函数。

最后在调用的时候,主要包含一个std::swap,确保标准的swap可以使用,最后赤裸裸的调用swap。这样C++可以自动调用最合适的swap。

条款26: Postpone variable definitions as long as possible.
  • 不应该只是延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。(先用default 构造函数再赋值的效率低于 在初始化时直接指定初始值)
条款27: Minimize casting.
  • const_cast 用来将对象的常量性移除,也是唯一拥有此能力C++类型转换符。
  • dynamic_cast 主要用来执行“安全向下转型”,也就是用来决定某对象是否归属于继承体系中的某个类型。
  • reinterpret_cast 意图执行低级转型,实际动作取决于编译器,所以表示它是不可以移植的。
  • static_cast 用来强迫隐式转换。
  • 任何一个类型转换,往往真的令编译器编译出运行期间执行的代码。
class Base{}
class derived: public Base{}
derived d;
Base* pb = &d;

这里的两个指针值有时候并不相同。会在derived* 指针上有个偏移量来取得正确的Base* 的指针值。

class window{
public:
	virtual void onResize(){}
};
class sepcialWindow: public window{
public:
	virtual void onResize(){
		static_cast<window>(*this).onResize();}
		//这是不可行的!
};

子类的onResize() 函数,执行的对象并非是当前对象。他是在“当前对象之base class成分” 的副本上调用window::onResize 然后,又在当前对象上执行sepcialWindow 的专属动作。所以这样会导致对象进入一种“伤残”状态。base class 成分的更改没有落实,但是derived class 成分的更改落实了。下面的才是正确的:

class sepcialWindow: public window{
public:
	virtual void onResize(){
		window::onResize();}
};

关于dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class 操作函数,但是你手头上只有一个指向base 的pointer。有两个一般的方法可以避免这个问题:

  • 使用容器并在其中直接存储只想derived class 对象的指针。
  • 在base class 提供virtual 函数做你想对各个window 派生类做的事情。
条款28: Avoid return “handles” to object internals.
  • 避免返回handles (包括references,指针,迭代器)指向对象内部。遵守这个条款可以增加封装性,帮助const 成员函数的行为像个const,并将发生dangling Handel 的可能性降到最低。
条款29: Strive for exception-safe code.

“异常安全” 应该满足以下两点:

  • 不泄露任何资源。
  • 不允许数据败坏。

解决资源泄露问题可以使用 Lock class。
异常安全函数(Exception-safe functions)提供三个保证之一:

基本承诺:保证程序内的事务保持在有效状态下,但是显示状态是不可预料的。
强烈保证:如果异常抛出,那么程序状态不改变;如果函数成功,就是完全成功,如果失败,就回复到调用函数之前。
不抛掷保证:承诺绝不抛出异常,因为总能完成原先承诺的功能。作用域内置类型身上的所有操作都提供nothrow保证。

  • 强烈保证往往能够以 copy and swap 实现出来,但强烈保证并非对所有函数都可实现或者具备现实意义。
  • 函数提供的 异常安全保证 通常最高只等于其所调用的各个函数的 异常安全保证 中的最弱者。
条款30: Understand the ins and outs of inlining.

对于inline 函数,编译器可以对其执行语境相关的最优化。

  • inline 函数通常被置于头文件内,因为inlining 在大多数C++ 程序中是编译期行为。
  • 将大多数的inling 限制在小型,被频繁调用的函数身上。
  • 不要只是因为function templates 出现在头文件,就将他们声明为inline。
  • 构造函数和析构函数,往往不是inline 的。会出现多副本问题。
条款31: Minimize compilation dependencies between files.
  • 编译依存性的最小本质:让头文件尽可能的自我满足,如果做不到,则让它与其它文件内的声明式相依(而非定义式)。

如果使用object references 或者 object pointers 就可以完成认为,就不要使用objects。可以只靠一个类型的声明式就能定义出指向该类型的references,但是定义objects,就要用到定义式。
如果可能,就用class 声明式来替换class定义式。
为声明式和定义式提供不同的头文件。(.h 文件只包含声明式,对应的定义式分布在不同的头文件内)

  • 程序库头文件应该以 “完全且仅有声明式”的形式存在。
条款32: Make sure public inheritance models “is-a.”
  • 适用于base class 身上的每件事情,一定也适应于derived classes 身上,因为每一个derived class对象也式一个base class对象。
条款33: Avoid hiding inherited names.
  • derived class 内的名称会遮掩base classes 内的名称,在public 继承下从来没有人希望如此。
  • 为了让被遮盖的名称重现,可以使用using 声明式;为了只继承特定的函数,可以使用转交函数
条款34: Differentiate between inheritance of interface an inheritance of implementation.
  • 成员函数的接口总是会被继承的。
  • 声明一个pure virtual 函数的目的是为了让derived classes只继承函数接口。
  • 声明非纯虚函数的目的是为了让derived classes 继承该函数的接口和缺省实现。
  • 声明non-virtual 函数的目的是为了令 derived classes 继承函数的接口以及一份强制性实现。
条款35: Consider alternatives to virtual functions.
  • 利用Non-Virtual Interface 手法来实现 Template Method 模式。利用non-virtual 的public 成员函数来调用 virtual 的 private 函数来做实际工作,称之为 non-virtual interface(NVI)手法。把这个non-virtual 函数称之为virtual 函数的 wrapper。NVI手法的一个优点是可以确保在 virtual 函数进行真正工作之前,设定好适当的场景,结束后清除场景。
  • 利用 Function Pointers 实现 Strategy 模式。在每个类构造时接受一个指针来指向一个函数。
条款36: Never redefine an inherited non-virtual function.
条款37: Never redefine a function’s inherited default parameter value.

因为绝不能重新定义个non-virtual 函数,所以这里只讨论带有缺省参数值的virtual function。

  • virtual 函数系统动态绑定,而缺省参数值是静态绑定的。
class shape{
public:
	virtual void draw(shapecolor color = red) const = 0;
};
class rectangle: public shape{
public:
	virtual void draw(shapecolor color = green) const;
}
// cosider the case as follow:
shape* ps = new shape;
shape* pc = new rectangle;
pc->draw; // in fact: rectangle::draw(red);
// function belongs to rectangle but parameter is from base class;
// pc的静态类型是base class,所以其缺省值是静态类型对应的。但是在执行的时候,做的是动态;
//运行的函数是derived class的;因此会不符合逻辑;可以使用NVI 的方法:
class shape{
public:
	void draw(shapecolor color = red) const{
		doDraw(color);
	}
private:
	virtual void doDraw(shapecolor color) const = 0;
};
class rectangle: public shape{
private:
	virtual void doDraw(shapecolor color) const;
}
条款38: Model “has- a” or “is-implemented-in-terms-of” through composition.
  • 复合的意义和public 继承完全不同。
  • 在应用领域,复合意味着has-a,但是在现实领域,复合意味着is-implemented-in-terms-of。
条款39: Use private inheritance judiciously.
  • Private 继承意味着implemented-in-terms-of (根据某物实现出)。如果D 以private 形式继承B,意味着采用class B内已经备妥的某些特性。

“空白基类最优化(EBO empty base optimization)”:对于“大小为零之独立(非附属)对象”,C++ 编译器会给它一字节的大小来占位;但对于非独立的,例如是继承而来的,会有 EBO,但是一般只在单一继承下才可行。

class base{} 
class derivedClass{
public: 
	base x; // sizeof(base) == 1,默认安插一个char 到空对象内;
	int b;
}
class derivedC:private base{
private: int x;
} // 这里的sizeof(derivedC) == sizeof(int); it is empty base optimization;
  • private 继承意味 is-implemented-in-terms of 根据某物实现出。通常比复合的级别低,但是当derived class 需要访问protected base class 的成员,或者需要重新定义继承而来的virtual 函数时,这么设计是合理的。
条款40: Use multiple inheritance judiciously.

对于“菱形多重继承”,是否应从base class 继承多份。为了避免多份复制,采用virtual 继承,必须让带有此数据的class 成为一个virtual base class。为了这么做,必须让所有直接继承自它的classes 采用 virtual 继承:

class file{};
class inputfile: virtual public file{};
class outputfile: virtual public file{};
class IOfile: public inputfile, public outputfile{};
  • 不可以对所有public 继承都采用virtual 的形式。1. 因为virtual 继承所产生的对象会比一般的体积大得多。访问成员变量也会慢很多,virtual 是需要付出代价的。2. virtual 继承的成本还存在在初始化的时候,virtual base 的初始化责任是由继承体系中最低层的(most derived)class负责的。这就需要classes 若派生自virtual bases 而需要初始化,必须认知其virtual bases。当一个新的derived class 加入继承体系中,必须承担其virtual bases的初始化责任。
  • 多重继承的正当用途有很多。其中一个“public 继承某个interface class” 和“private 继承某个协助实现的class” 的两相结合。
条款41: Understand implicit interfaces and compile-time polymorphism.
  • Templates 和classes 都支持接口和多态
  • 对classes 而言接口是显示的,以函数签名为中心,多态是通过virtual 函数发生于运行期。
  • 对template 参数而言,接口是隐式的,奠基于有效表达式。多态则通过template 具现华和函数重载解析发生于编译期。
条款42: Understand the two meanings of typename.
  • 当声明template 类型参数时,class 和 typename 的意义完全相同:
template<class T> class widget;
template<typename T>class widget// 二者完全相同

但有时候必须使用typename:

template<typename C>
void print2nd(const C& container){
C::const_iterator* x;
} // 会产生歧义,因为如果const_iterator 是一个static 变量,那么就会理解成相乘
// 因此需要增加一个typename 来说明
typename C::const_iterator* x(container.begin());

typename 只能被用来验明嵌套从属类型名称,其他的名称不应该有它的存在。另:typename 不可以出现在base classes list 内的嵌套从属类型名称之前,也不可以在member initialization list 中作为base class修饰符。

template<typename T>
class Derived:public Base<T>::Nested{//base class list 中是不允许有typename
public: 
	explicit Derived(int x): Base<T>::Nested(x)// mem.init.list中也不允许
	{
		typename Base<T>::Nested temp;//这里需要增加;
}

同时一个例子来使用typedef

template<typename IterT>
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);// 可以少打很多;
  • 使用typename 标识嵌套从属类型名称,注意一些例外。
条款44: Factor parameter-independent code out of templates.
  • Templates 生成多个class 和多个函数,任何template代码都不应该与某个造成膨胀的template 参数产生依存关系。
  • 因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或者class成员变量替换template 参数。
  • 因类型参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同的二进制表述的具现类型共享实现码。
条款45: Use member function templates to accept “all compatible types.”
  • 请使用member function templates生成“可以接受所有兼容类型”的函数。
  • 如果你声明member templates 用于“泛化copy构造”或者“泛化assignment操作”,还是需要声明正常的copy构造函数和copy assignment操作符。
条款46: Define non-member functions inside templates when type conversions are desired.

如24 条款所提到的,这里把对应的类和操作模板化:

template<typename T>
class Rational{
public:
	Rational(const T& numerator = 0, const T& denominator = 1);
	const T numerator() const;
	const T denominator() const;
};
template<typename T>
const Rational<T> operator*(const Rational<T>&lef, const Rational<T>&rig){}
//
Rational<int> onehalf(1, 2);
Rational<int> result = onehalf * 2; //error, 无法通过编译

  • 当编写一个class template,而它所提供“于template 相关的”函数支持所有参数的隐式类型转化时,将那些函数定义为class template内部的friend 函数。
条款47: Use traits classes for information about types.
  • Trait class 使得类型相关信息在编译期间可用,它们以templates 和 templates 特化完成实现。
  • 整合重载技术之后,traits classes可能在编译期间对类型执行if else测试。
条款48: Be aware of template metaprogramming.
  • Template metaprogramming 可以将工作由运行期移往编译期,因而得以实现早期错误的侦测和更高的执行效率。
  • TMP 可以被用来生成基于政策选择组合的客户定制代码,用来避免生成某些特殊类型并不适合的代码。
条款49: Understand the behavior of the new-handler.
  • set_new_handler 允许客户指定一个函数,在内存分配无法满足时被调用。
  • Nothrow new 是一个颇为局限的工具,只能适用于内存分配,后继的构造函数调用可能还是抛出异常。
条款50: Understand when it makes sense to replace new and delete.
  • 用来检测运用上的错误;为了强化效能;为了统计使用上的数据;为了增加分配和归还的速度;为了降低缺省内存管理器带来的空间额外开销;为了弥补缺省分配器中的非最佳齐位;将相关对象成簇集中;为了获得非传统的行为。
条款51: Adhere to convention when writing new and delete.
  • C++ 规定,即使客户要求0 bytes 也会返回一个合法指针,这是因为new 操作将size == 0 时,改变为了size = 1,分配一个size = 1 的空间;
  • C++ 保证“删除null 指针永远安全”,所以必须兑现这项承诺。
  • operator new 应该包含一个无限循环,并在其中尝试分配内存,如果无法满足内存需求,调用new-handler,同时能处理0bytes 和处理错误申请;
  • operator delete 应该在收到null 时什么都不做;
条款52: Write placement delete if you write placement new.
条款53: Pay attention to compiler warnings.
条款54: Familiarize yourself with the standard library including TR1.
条款55: Familiarize yourself with the standard library including TR1.
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值