effective C++ 3rd 笔记(一)
时间:2011-04-05 来源:Atela
条款01:视C++为一个语言联邦 : C, Object-Oriented C++, Template C++, STL
条款02:尽量以const,enum,inline 替换#define:
1.对于单纯常量,最好以const对象或enum替换#define
2.对于形似函数的宏(macros),最好改用inline函数替换#define。
#define ASPECT_RATIO 1.653
const double AspectRatio = 1.653;
1)用#define定义的名字可能没有进入记号表,编译出错,不方便追踪
2)用const定义常量将得到更精简的目标代码(object code),因为预处理器处理#define是会盲目的将宏名称替换为1.653导致目标码出现多份1.653若用const则不会出现这种问题。
当用常量替换#define有两种特殊的情况。
1)定义常量指针。由于常量定义式通常放在头文件内(以便被不同源码含入),因此有必要将指针声明为const。
const char* const authorName = “Scott Meyer”;
string对象通常比char*-base合宜,所以authorName往往定义成这样:const std::string authorName(“Scott Meyer”);
2)class专属常量。为了将常量的作用域限制于class内,和确保此常量至多只有一份实体,必须让它成为一个static成员:
class GamePlayer {
private:
static const int NumTurns = 5;
int scores[NumTurns];
...
}
通常static & const& 整数类型(ints,chars,bools)类型成员如果不取地址,可以声明并使用而无需定义。但如果你取某个class专属常量的地址,或你的编译器却(不正确地)坚持要看到一个定义式就必须提供如下定义式
const int GamePlayer::NumTurns; //放到实现文件,因为在声明时已获得初值,不能再设初值。
我们无法用#define 创建class专属常量,因为#define不重视作用域scope。
旧时编译器也许不支持上述语法,不允许static成员在声明时提供初值。
则将初值放到定义式里,但编译器坚持必须在编译期间知道数组score[]大小,万一你的编译器不允许static 整数型class常量完成 in class初值设定,可改用 the enum hack补偿做法。
class GamePlayer{
private:
enum{ NumTurns=5 };
int scores[NumTurns];
}
1)取enum和#define地址一样,不合法。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。enums和#defines一样不会导致非必要的内存分配。
2)实用,很多代码用了。 enum hack是 模板元编程template metaprogramming的基础技术。
另一个常见的#define误用情况是用它实现宏。宏看起来像函数,但不会招致函数调用带来的额外开销。
#define MAX(a,b) f((a)>(b)?(a):(b))
int a =5, b = 0;
MAX(++a, b);//a被累加二次
MAX(++a, b+10);//a被累加一次
替换为内联模板函数:宏的效率和类型安全,可预料行为
template<typename T>
inline void MAX(const T&a,const T&b)
{
f(a>b?a:b);
}
条款03: 尽可能实用const
const Rational operator* (const Rational& lhs, const Rational& rhs);//为什么返回const对象?
Rational a, b, c;
…
(a * b) = c;//
if(a * b = c) … // 其实想做比较操作,返回const可预防此类问题
const成员函数:可被const对象调用。两个函数如果只是常量性不同,是可以被重载。
可用mutable释放掉non-static成员变量的bitwise constness约束(即mutable修饰的成员变量可被const成员函数修改)。
在const和non-const成员函数中避免重复:
class TextBlock {
public: …
const char& operator[] (std::size_t position) const {
…
return text[position];
}
char& operator[] (std::size_t position) { //non-const成员函数调用const成员函数避免代码重复
return
const_cast<char&>(
static_cast<const TextBlock&>(*this)
[position]);
}
private: std::string text';
};
不能令const成员函数调用non-const成员函数,对象有可能被改动。
条款04: 确定对象被使用前已先被初始化。
1.对内置类型手工初始化
2.构造函数最好使用成员初值列 member initialization list,而不要在构造函数体内用赋值操作。初值列中列出的成员变量次序要跟在class中声明次序一致。
3.为免除跨编译单元之初始化次序问题,用local static 对象替换non-local static 对象
class A {
public: A(const string&);
private:
string str;
int num;
}
A::A(const string &s){
str=s; //赋值而非初始化,在初值列中调用一次default构造函数,函数体内一次赋值操作
num=0'; //内置类型不保证。
}
C++规定对象的成员变量初始化动作发生在进入构造函数体之前。如果成员变量是const或引用,则一定要初始化,而不能被赋值。
A::A(const string &s):str(s), //值调用一次copy构造函数
num(0){}
static对象:从被构造到程序结束为止。main()结束时自动析构。
包括global对象,namespace内的对象,在class内、函数内、及在file作用域内被声明为static的对象。
local static对象:函数内的static对象
non-local static对象:其他
如果某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,则可能未被初始化。C++对”定义于不同便一单元内的non-local static对象“的初始化次序无明确定义。
class FileSystem {
public: …
std::size_t numDisks() const;
};
extern FileSystem tfs;
//另一编译单元
class Directory {
public:
Directory(params);
};
Directory::Directory(params) {
std::size_t disks = tfs.numDisks();
}
Directory tempDir (params);
解决方案:singleton模式,用local static对象替换non-local static对象
C++保证,函数内的local static 对象会在该函数被调用期间,首次遇上该对象定义式时被初始化。
class FileSystem {…};
FileSystem& tfs() {
static FileSystem fs;
return fs;}
class Directory {…};
Directory::Directory (params) {
std::size_t disks = tfs().numDisks();
};
Directory& tempDir() {
static Directory td;
return td;}
条款05:了解C++默默编写并调用哪些函数
编译器暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
如果没有定义复制构造函数,编译器就会合成一个,即使已经定义了其他的构造函数。
template<class T>
class NamedObject {
public: NamedObject(string &name, const T &value):nameValue(name),objectValue(value){}
private: string &nameValue;
const T objectValue;
};
string newDog(“aaa”);
string oldDod(“bbb”);
NamedObject<int> p(newDog,2);
NamedObject<int> s(oldDog, 36);
p = s; //编译失败
在内含引用或const成员的class内支持赋值操作符,你必须自己定义copy assignm操作符。
如果某个base class将copy assignment操作符声明为private,编译器拒绝为其derived classes生成一个copy assignment操作符。
条款06: 若不想使用编译器自动生成的函数,就该明确拒绝。
方案:将相应成员函数声明为private并不予实现。或使用Uncopyable这样的base class
明确声明一个成员函数,阻止编译器暗自生成,另其为private,阻止外部调用。
但成员函数和friend仍然可以反问,所以只声明而不定义它们,如不慎调用,则会得到一个连接错误。
将连接期错误移到编译器:
class Uncopyable{
protected: Uncopyable(){} //允许derived对象构造和析构
~Uncopyablel(){}
private: Uncopyable(const Uncopyable&); //但阻止copying
Uncopyable& operator=(const Uncopyable&);
};
class HomeForSale:private Uncopyable { …};
当尝试拷贝HomeForSale对象时,自动生成copy构造函数和copy assignment操作符,并尝试调用基类对于版本,但失败。。。
条款07: 为多态基类声明virtual析构函数
1.若果class带有任何virtual函数,它就应该拥有一个virtual析构函数
2.class的设计目的不是作为base class使用或不是为了具备多态性,就不改声明为virtual析构函数
C++明确指出当derived对象经过base class指针被删除,而该base class带有一个non-virtual析构函数,则其结果未定义的:局部销毁。
每一个带有virtual函数的class都有一个相应的由函数指针构成的数组vtbl(virtual table). 当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr (virtual table pointer)所指的那个vtbl,编译器在其中寻找适当的函数指针。无端声明析构函数为virtual会增加为vptr的付出的空间补偿。
class SpecialString: public std::string{ //馊主意!
};
SpecialString *pss = new SpecialString(“aaa”);
std::string *ps=pss;
delete ps;//未定义!
如果企图继承一标准容器或任何其他带有non-virtual析构函数的class,拒绝诱惑吧!!!
为你希望成为抽象(不能实体化)的那个class声明一个pure virtual析构函数
class AWOV {
public: virtual ~AWOV() = 0;
};
必须提供一份定义 AWOV::AWOV() {}
不然派生类的析构函数调用base class的析构函数时,不然连接器会报错。
条款08:别让异常逃离析构函数
1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能突出异常,析构函数应该捕捉任何异常,然后吞下(不传播)或结束程序。
2.如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。
栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。在执行析构函数前如果已经有一个异常,析构函数中又抛出一个异常,将会导致调用标准库 terminate 函数强制从程序非正常退出。
class DBConnection {
public : static DBConnection create();
void close();
};
确保DBConnection不忘记调用close()
class DBConn {
public:
~DBConn(){
db.close();}
private: DBConnection db;
};
DBConn dbc(DBConnection::create());
如果析构函数抛出异常:
1) 强迫结束
DBConn::~DBConn(){
try { db.close();}
catch(…) {
制作运转记录,记下对close的调用失败
std::abort();
}
2)吞下异常
DBConn::~DBConn(){
try { db.close();}
catch(…) {
制作运转记录,记下对close的调用失败
}
3)较佳的策略,客户自己调用普通函数
class DBConn {
public:
void close(){
db.close();
closed = true;
}
~DBConn(){
if(!closed) {
try { db.close(); }
catch(…){
制作运转记录,记下对close的调用失败
abort或吞下异常
}
}
private: DBConnection db;
bool closed;
};
条款09:绝不在构造和析构函数中调用virtual函数
因为这类调用从不下降至derived class (比起当前执行构造函数和析构函数的那层)
class Transaciton {
public:Transaciton();
virtual void logTransaciton() const = 0;
};
Transaciton::Transaciton(){
logTransaciton();
}
class BuyTransaciton:public Transaciton{
public: virtual void logTransaciton() const;
};
class SellTransaciton:public Transaciton{
public: virtual void logTransaciton() const;
};
BuyTransaciton b; //基类构造函数调用的是Transaciton::logTransaciton();
BuyTransaciton专属成分还未初始化,最安全的做法就是视而不见,当成Transaciton类。
对象在derived class 构造函数开始执行前不会成为一个derived class。
析构函数类似,在进入base class 析构函数后对象就成为一个base class对象。
解决方案:令derived class 将必要的构造信息上传至base class构造函数,来替换无法使用virtul函数从base class向下调用。
class Transaciton {public:
explicit Transaciton(const string &loginfo);
void logTransaciton(const string &loginfo) const;
};
Transaciton::Transaciton(const string &loginfo){
logTransaciton(loginfo);
}
class BuyTransaciton:public Transaciton{
public:
BuyTransaciton( parameters): Transaciton( createLogString(parameters)) {…}
private:
static string createLogString(parameters);
};
条款10: 令operator= 返回一个reference to *this
条款11: 在operator= 中处理自我赋值
class Bitmap {…};
class Widget {
private: Bitmap *pb;
};
Widget& Widget::operator=(const Widget &rhs) {
delete pb; //没有进行自我赋值检测
pb = new Bitmap(*rhs.pb);//不具备异常安全,发生异常后,pb指向一块已删除的内存
return *this;
}
解决:证同测试
Widget& Widget::operator=(const Widget &rhs) {//不具备异常安全
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);//不具备异常安全
return *this;
}
Widget& Widget::operator=(const Widget &rhs) {
Bitmap *pOrig = pb; //异常安全,又能处理自我赋值,当自我赋值发生频率较高时,可加入证同测试。
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
另一替换方案确保异常安全,又能处理自我赋值:copy and swap技术
class Widget{
void swap(Widget &rhs);
};
Widget& Widget::operator=(const Widget &rhs){
Widget temp(rhs);
swap(temp);
return *this;
}
Widget& Widget::operator=(const Widget rhs){ //另一较高效写法,by-value
swap(rhs);
return *this;
}
条款12: 复制对象时勿忘其每一个成分
1.copying 函数应该确保赋值”对象内所有成员变量”及“所有base class成分”
2.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个函数共同调用。
为derived class编写copying函数时,必须复制其base class成分。
class Customer {
public:
Customer(const Customer &rhs);
Customer& operator=(const Customer &rhs);
private: string name;
};
class PriorityCustomer: public Customer {
public:
priorityCustomer(const priorityCustomer &rhs);
priorityCustomer& operator=(const priorityCustomer &rhs);
private: int priority;
};
priorityCustomer::priorityCustomer(const priorityCustomer &rhs)
:Customer(rhs), //调用base class的copy构造函数,如省略,则调用默认的构造函数,基类部分为默认值
priority(rhs.priority)
{ }
priorityCustomer& priorityCustomer::operator=(const priorityCustomer &rhs) {
Customer::operator=(rhs); //可先进行证同测试,成员如有指针
priority = rhs.priority;
return *this;
}