首页 > 代码库 > 《effective c++》读书笔记1
《effective c++》读书笔记1
条款1:视C++为一个语言联邦
C++包括这四个部分:
l C
l Object-Oriented C++
l Template C++
l STL
n C++高效编程守则视状况而变化,取决于你使用C++的哪一部分
条款2:尽量以const,enum,inline替换#define
下面这个宏夹带着宏实参,调用函数f:
#define CALL_WITH_MAX (a,b) f((a)>(b)?(a):(b))
纵使为所有实参加上小括号,看看下面不可思议的事情
int a=5,b=0;
CALL_WITH_MAX(++a,b); //a被累加两次
CALL_WITH_MAX(++a,b+10) //a被累加一次
可以改成内联函数的形式:
template <class T>
inline void callWithMax(const T&a,constT&b)
{
f(a>b?a:b);
}
n 对于单纯常量,最好以const对象或enums替换#define
n 对于形似函数的宏,最好改用inline函数替换#define
条款3:尽可能使用const
vector<int> vec;
const vector<int> ::iterator iter=vec.begin();
*iter=10; //正确
iter++;//错误
const成员函数不能修改成员变量,但变量加上mutable后,const成员函数也可以修改其值
vector<int> ::const_iterator citer=vec.begin();
*citer=10; //错误
++citer; //正确
class TextBlock
{
public:
size_tlength() const;
private:
char *pText;
mutablestd::size_t textLength;
};
std::size_t CTextBlock::length() const
{
textLength=std::strlen(pText); //正确
}
n 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体
n 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复。
条款4:确定对象被使用前已被初始化
class PhoneNumber{…};
class ABEntry
{
public:
ABEntry(conststd::string &name,const std::string &string &address,conststd::list<PhoneNumber> &phones);
private:
std::stringtheName;
std::list<PhoneNumber>thePhones;
intnumTimesConsulted;
};
ABEntry::ABEntry(const std::string&name,const std::string &address,const std::list <PhoneNumber>&phone)
{
theName=name;
theAddress=address;
thePhones=phones;
numTimeConsulted=0;
}
上面的构造函数在进入函数体之前会先调用默认构造函数,然后在函数体内赋值,这样的方式效率较低,更好的做法是:
ABEntry::ABEntry(const std::string&name,const std::string &address,const std::list<PhoneNumber>&phones):theName(name),theAddress(address),thePhones(phones),numTimeConsulted(0)
{}
成员初始化列表的效率更高,原因是初值列中各个成员变量被拿去作为各成员变量之构造函数的实参。theNames以name为初值进行copy构造,theAddress以address为初值进行copy构造,thePhones以Phones为初值进行copy构造。
对于内置类型,其初始化和赋值的成本相同,但为了一致性,还是要放在初始化列表中初始化。
n 为内置类型对象进行手工初始化,因为C++不保证初始化它们
n 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作,初值列列出的成员变量,其排列次序应该和他们在class中的声明次序相同。
条款5:了解C++默默编写并调用哪些函数
n 编译器会暗自地为class创建default构造函数,copy构造函数,copyassignment操作符,以及析构函数
条款6:若不想使用编译器自动生成的函数,就应该明确拒绝
如果让一个类不能进行拷贝或者进行拷贝赋值,采用的办法可以是:把拷贝构造函数和赋值函数声明为private.这样既阻止了编译器为其生成默认版本,也阻止了人们调用它。
条款7:为多态基类声明virtual析构函数
无端地将所有calsses的析构函数声明为virtual,就像从未声明他们为virtual一样,都是错误的。许多人的心得是:只有当class内至少有一个virtual函数时,才为它声明virtual析构函数。
n 带有多态性质的基类应该声明一个虚的析构函数,如果类带有任何虚函数,它就应该有一个虚析构函数。
n 类的设计不是作为基类使用或者不是为了具有多态性,就不该声明序析构函数。
条款8:别让异常逃离析构函数
析构函数吐出异常可能会导致程序提前结束或者出现不明确的行为。
n 析构函数绝对不要抛出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下他们或者结束程序。
n 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数执行该操作。
条款9:绝不在构造和析构过程中调用virtual函数
基类构造函数的执行更早于派生类构造函数,当基类构造函数执行时派生类的成员变量尚未初始化。如果此期间调用的虚函数下降到派生类阶层,派生类的函数要使用本地的成员变量,而这些成员变量还没有初始化。
n 在构造和析构期间不要调用虚函数,因为这类调用从不下降到派生类。
条款10:令operator=返回一个*this的引用
class Widget
{
public:
Widget&operator+=(const Widget & rhs)
{
…
return*this;
}
Widget& operator=(int rhs)
{
…
return*this;
}
};
条款11:在operator=中处理”自我赋值”
有类定义如下:
class Bitmap{…}
class Widget
{
…
private:
Bitmap*pb;
};
下面是operator=的实现代码,表面上看起来合理,但自我赋值出现时并不安全。
Widget &
Widget::operator=(const Widget &rhs)
{
delete pb;
pb=new Bitmap(*ths.pb);
return *this;
}
上面的代码问题在于如果自我赋值,则对象的内存会被释放掉。阻止这种错误,可以再赋值操作符中加上一个测试即可。
Widget & Widget::operator =(const Widget&rhs)
{
if(this==rhs)
return*this;
deletepb;
pb=newBitmap(*rhs.pb);
return *this;
}
上面的代码是自我赋值安全的,但存在的问题是未能保证异常安全,因为new Bitmap一句如果出现了异常,结果将出现错误。
下面是自我赋值安全而且异常安全的代码:
Widget &Widget ::operator=(const Widget&rhs)
{
Bitmap *pOrig=pb;
pb=new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
这时,如果new Bitmao抛出异常,pb保持原状,这段代码也能处理自我赋值。
条款12:复制对象时勿忘其每一个成分
为派生类撰写拷贝函数时,必须小心地赋值其基类成分。那些成分往往是private,所以你无法直接访问它们,应该让派生类的拷贝函数调用相应的基类函数。
PriorityCustomer::PriorityCustomer(constPriorityCustomer & rhs):Customer(rhs),priority(rhs.priority)
{
;
}
PriorityCustomer&PriorityCustomer::operator =(const PriorityCustomer &rhs)
{
Customer::operator=(rhs);
priority=rhs.priority;
return *this;
}
拷贝函数应该确保复制对象内的所有成员变量及所有基类成分。
《effective c++》读书笔记1