C++effective读书笔记(待更新)

一、让自己习惯C++

条款1:尽量用const ,enum和inline 而不用#define(尽量用编译器而不用预处理)

  1. 对于单纯常量,以const,enum替换#defines
  2. 对于形似函数的宏(macros),改用inline函数替换#defines

enum

//实现编译期间就要知道数组的大小功能
class GAmePlayer
{
private:
	static const int NumTurns = 5;//常量声明式,还未定义!static要在类外定义
	int scores[NumTurns];//使用该常量
};
const int GAmePlayer::NumTurns;//声明时已经有了初值,定义时不能有了。

//也可以利用nums实现
class GamePlayer
{
	//enum理解为记号
	enum {NumTurns = 5};//令NumnTurns成为5的记号
	int scores[NumTurns];
	...
};

const 

const关键点:

  1. const T * t表示被指物T不可改变
  2. T * t const 表示指针t不可改变
  3. const T * t const指针和被指物均不可改变.

STL的const

const成员函数(相对应mutable:可变的)

  1. 标记该函数可以作用于const对象,使操作const对象(改动对象)成为可能(为什么?)
  2. 两个成员函数如果只有常量性不同,也是可以被重载的!只不过使用的时候要用const区分
  3. 变量声明为mutable后,即使在const成员函数内,也可能总是被修改!

eg:

//两个函数如果只是常量性不同,可以被重载
class TextBlock
{
private:
	string text;
public:
	...;
	//const不同也能重载
	const char& operator[](size_t position) const { return text[position]; }
	char& operator[](size_t position) { return text[position]; }
};

//使用
TextBlock tb("Hello");
cout << tb[0];
tb[0] = 'x';

const TextBlock ctb("World");
cout << ctb[0];
ctb[0] = 'x;'//错误!试图写一个const类型变量

note:

non-const operator[]的返回类型是个reference to char,不是char,如果是,tb[0] = 'x'通不过编译!

原因:如果函数的返回类型是个内置类型,改动函数返回值是不合法的。

1、非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误)
2、表示成员函数隐含传入的this指针为const指针决定了在该成员函数中,
    任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用

3、唯一的例外是对于mutable修饰的成员。
    加了const的成员函数可以被非const对象和const对象调用
    但不加const的成员函数只能被非const对象调用 

char getData() const
{
    return this->letter;
}
const double ASPECT_RATIO = 1.653;
//对于定义指针常量
const char * const authorName = "Scott Meyers";
//对于类中的常量
class EngineeringConstants 
{   // this goes in the class
    private: // header file
        static const double FUDGE_FACTOR;//静态变量还要在类外定义
        ...
};
// this goes in the class implementation file
const double EngineeringConstants::FUDGE_FACTOR = 1.35;

const总结

  1. const用于:对象、函数参数、函数返回类型、成员函数

  2. 当const和non-const 成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

inline 

 犯过的缺陷语句:无论什么时候你写了象这样的宏,你必须记住在写宏体时对每个参数都要加上括号

#define max(a,b) ((a) > (b) ? (a) : (b))

替代:以用普通函数实现宏的效率,再加上可预计的行为和类型安全,这就是内联函数

inline int max(int a, int b) { return a > b ? a : b; }

//进一步改进,实现对不同类型
template<class T>
inline const T& max(const T& a, const T& b) //因为不知道T的类型,返回时传递引用可以提高效率
{ 
    return a > b ? a : b; 
}

 条款4 初始化和赋值

构造函数对成员变量的初始化是在进入构造函数内之前!如果在构造函数内,是赋值。前者比较好(效率更高)!

  • 总是使用成员初始值列
ABEntry::ABEntry(const string& name, 
const string& address, 
const list<PhoneNumber>& phones)
:theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{
}
  •  对于non-local static对象的初始化次序(函数内的static对象称为local static对象,因为对函数而言是local)

static对象:global对象、定义域namspace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。

二、构造、析构、赋值运算 

条款6:若不想使用编译器自动生成的函数(copy构造函数和copy assignment操作符),就该明确拒绝

将相应的成员函数声明为private并且不予实现

条款7:为多态基类(polymorphic base classes)声明virtual析构函数

  1. polymorphic base classes应该声明一个virtual析构函数。如果class带有任何virtaul函数,他就应该拥有一个virtual析构函数
  2. Classes的设计目的如果不是作为base classes使用,或不是为了具备polymorphically,就不该声明virtual析构函数

条款8:析构函数遇到异常时,要暂停程序

class提供普通函数捕捉异常

//释放DBConnection类
class DBConn
{
private:
	DBConnection db;
	bool closed;
	void close()
	{
		db.close(); //供客户使用的新函数
		closed = true;
	}
	~DBConn()
	{
		if (!closed)
		{
			try
			{
				db.close();
			}
			catch (...)
			{
				//制作运转记录,记下对close的调用失败
			}
		}
	}
};

 条款9:绝不在构造和析构过程中调用virtual函数

这类调用从不下降至deri ved class

条款10:令operator=返回一个reference to *this

令赋值操作符assignment operator返回一个reference to *this

条款11:在operator=中处理“自我赋值”

  1. 确保当对象自我赋值时operator=有良好行为。技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap
  2. 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时(例如多个指针指向同一块内容),其行为仍然正确。
Widget& Widget::operator=(const Widget& rhs)
{
	Bitmap* pOrg = pb;//记住原先的pb,指向同一块内容
	pb = new Bitmap(*rhs.pb);//令pb指向*pb的一个复件(副本)
	delete pOrig; //删除原先的pb
        return *this;
}

 条款12:Copy all parts of an object

  1. Copying函数应该确保复制"对象内的所有成员变量”及“所有base class成分”
  2. 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数,并由两个coping函数共同调用(减少重复性代码)。

三、资源管理(将使用过的资源交还给系统)

条款13:Use objects to manage resources

对于资源在heap-based的资源

思想,类似于利用对象的析构(自动)来释放资源。管理对象(managing object)例如智能指针shared_ptr

RAII:资源获得时机便是初始化时机

  1. 获得资源后立刻放进管理对象
  2. 管理对象运用析构函数确保资源被释放

条款14:在资源管理类中小心coping行为

并非所有资源都是heap-based,例如c的互斥器对象Mutex, shared_ptr不适用,要建立自己的资源管理类:

资源在构造期间获得,在析构期间释放

条款15:在资源管理类中提供对原始资源的访问

  1. APIs往往要求访问原始资源(raw resources),所以每一个RAII Class应该提供一个“”取得其所管理之资源“”的办法
  2. 对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。

条款16:成对使用new和delete时要采取相同形式

条款17:以独立语句将newed对象置入智能指针

  1. 以独立语句将newed对象存储于智能指针内,如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露
processWidget(std::trl::shared_ptr<Widget>(new Widget), priority());

当编译器:执行“”new Widget“” -->调用prioruty-->trl::shared_otr构造函数次序时,如果对priority函数调用异常,则new Widget返回的指针会遗失,因为它尚未被置入shared_ptr内。

解决:分离语句

std::trl::shared_ptr<Widget>(new Widget);
processWidget(pw, priority());

4、设计与声明

条款18:让接口易被使用,不易被误用

Investment* createInvestment();
//改为
std::trl::shared_ptr<Investmen> createInvestment();

实质上强迫客户将返回值存储于一个shared_ptr内,几乎消弭了忘记删除底部Investment对象(当它不再被使用时)的可能性。

shared_ptr提供的某个构造函数接受两个实参:一个是被管理的指针,另一个是引用次数变为0时将被调用的“”删除器“”。

条款19:设计class犹如涉及type

条款20:Prefer 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对象;

绝不要返回reference指向一个heap-allocated对象;

绝不要返回pointer或reference指向一个local stack对象而有可能同时需要多个这样的对象;

条款22:将成员变量声明为private

protected并不比public更具封装性

条款23:Prefer non-member non-friend functions to member functions

不增加可访问的数据量(private,enums,typedefs等),即加大了封装性

可以将non-member functions 和 class  放在同一个命名空间内。

将所有便利函数放在多个头文件内但隶属于同一个命名空间,意味着客户可以轻松扩展这一组便利函数

条款24:Declare non-member functions when type conversions should apply to all parameters

条款25:考虑写出一个不抛出异常的swap函数

  1. 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
  2. 如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes(而非templates),也请特化std::swap
  3. 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。
  4. 为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

五、实现

条款26:尽可能延后变量定义式的出现时间

条款27:尽量少做转型(casts)动作

转型风格通常有三种形式:

  1. C风格:(T) expression
  2. 函数风格:T(expression)
  3. C++还提供四种新式转型(c++style casts)
const_cast<T>(expression)//将对象的常量性移除!唯一由此能力的C++style转型操作符
dynamic_cast<T>(expression)//决定某对象是否归属继承体系中的某个类型。
reinterpret_cast<T>(expression)//不可移植。
static_cast<T>(expression)//强迫隐式类型转换,non-const-->const;void*-->typed*;int-->double

 新式优点:在代码中易被辨别出来;转型细化,易被编译器诊断错误

条款28:避免返回handles指向对象内部成分

  1. 避免返回handles(包括references、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。

条款29:为“异常安全”而努力是值得的(strive for exception-safe code)

条款30:透彻了解inlining的里里外外

不要只因为function templates出现在头文件,就将它们声明为inline。

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

六、继承与面向对象设计

条款32:确定你的public继承是is-a关系

条款33:避免遮掩继承而来的名称(Avoid hiding inherited names)

只要函数名称相同,派生类成员函数就会遮掩基类成员函数,可以使用using声明式达到继承重载函数的目标。

#include <cstring>
using namespace std;
class Base
{
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf1(int);
	virtual void mf2();
	void mf3();
	void mf3(double);
};

class Derived :public Base
{
public:
	using Base::mf1;//让Base class 内名为mf1和mf3的所有东西在Derived作用域内都可见(并且public)
	using Base::mf3;
	void mf3();
	void mf4();
};
  1. derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此
  2. 为了让被遮掩的名称再见天日,可使用using 声明式或转交函数(forwarding functions)

条款34:区分接口继承(inheritance of interface)和实现继承(inheritance of implementation)

有时想只继承成员函数的接口(pure virtual),有时又想继承声明和实现但是想override所继承的实现(impure virtual),有时又想继承声明和实现但是不允许覆写任何东西(non-virtual)。

条款35:考虑virtual函数以外的其他选择(consider alternatives to virtual functions)

条款36:绝不重新定义继承而来的non-virtual函数

条款37:绝不重新定义继承而来的缺省参数值

绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数-----你唯一应该覆写的东西----却是动态绑定

条款38:通过复合(composition)塑模出has-a或“根据某物实现出”

Model "has-a" or "is-implemented-in-terms-of"through composition

条款39:明智而审慎的使用private继承use private inheritance judiciously

  1. Private意味着is-implemented-in-terms-of。它通常比复合(composition)的级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
  2. 和复合不同,private继承可以改造empty base 最优化。这对致力于“对象尺寸最小化”的程序开发者而言,可能很重要。

条款40:明智而审慎的使用多重继承(MI)

七、模板与泛型编程 Templates and Generic Programming

条款41:了解隐式接口和编译期多态

隐式接口:在编译期,根据某个对象支持什么操作来隐式判断是什么对象。

面向对象编程世界总是以显式接口(explicit interfaces)和运行期多态(runtime polymorphism)即动态绑定解决问题

在Template及泛型编程的世界,与面向对象有根本上的不同,隐式接口(implicit interfaces)和编译期多态(compile-time polymorphism)更重要,因为templates事先并不知道具体的类型!

运行期多态和编译期多态之间的差异:类似于“哪一个重载函数该被调用,编译期”和“哪一个virtual函数该被绑定,运行期”。

条款42:了解typename的双重意义

从属名称:template内出现的名称依赖于某个template参数;

嵌套从属名称:从属名称在class内呈嵌套状;有可能导致解析(parsing)困难。

般性规则:任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放上关键字typename,

typename只被用来验明嵌套从属类型名称,其他名称不该有它存在,eg:

template<typename C>
void f(const C& container,//并非嵌套于任何“取决于template参数”的东西内,不允许使用typename
	typename C::iterator iter);//嵌套从属名称,一定要使用“typename”

 例外:typename不可以出现在base class list内的嵌套从属类型名称之前,也不可在member initialization list(成员初始值)中作为nase class修饰符。eg:

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
	{
		typename Base<T>::Nested temp;//嵌套从属名称,需要typename
	}
};

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

可在derived class templates内通过this-->“”指涉base class templates内的名称,或藉由一个明白写出的“base class资格修饰符”完成

条款44:将与参数无关的代码抽离templates

类似于,编写某个class和另一个class的某些部分相同,把共同部分搬移到新的class1去,然后使用继承和复合,令原先的classes取用这共同特性。

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

STL容器的迭代器几乎总是智能指针,无疑的你不会奢望使用++将一个内置指针从linked list的某个节点移到另一个结点,但这在list::iterators身上办得到。

  1. 请使用member function templates生成“可接受所有兼容类型”的函数
  2. 如果你声明member templates 用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。

条款46:需要类型转换时请为模板定义非成员函数

当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。

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

条款48:认识template元编程Be aware of template metaprogramming

TMP是编写template-based C++程序并执行于编译期的过程;即以C++写成、执行于C++编译器内的程序。

八、定制new和delete

条款49:了解new-handler的行为

  1. set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用

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

有许多理由需要写个自定义的new和delete,包括改善性能、对heap运用错误进行调试、收集heap使用信息

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

  1. operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果无法满足内存需求,就该调用new-handle。它也应该有能力处理0bytes申请。class专属版本则还应该处理“比正确大小更大的(错误)申请”
  2. operator delete应该在收到null指针时不做任何事情。class专属版本则还应该处理“比正确大小更大的(错误)申请”

条款52:写了placement new也要写placement delete

九、杂项讨论

条款53:不要轻忽编译器的警告

条款54:让自己熟悉包括TR1(C++ Technical Report 1 )在内的标准程序库

条款55:让自己熟悉Boost

猜你喜欢

转载自blog.csdn.net/vict_wang/article/details/81637048