<Effective C++>读书笔记-1

来源:互联网 发布:淘宝申请签约海外买手 编辑:程序博客网 时间:2024/04/30 11:56
条款01:视C++为一个语言联邦
条款02:尽量以const,enum,inline替换#define
条款03:尽可能使用const
条款04:确定对象被使用前已先被初始化
条款05:了解C++默默编写并调用哪些函数
什么时候empty class不再是个empty class呢?当C++处理过它之后.是的,如果你自己没有声明,编译器就会为它声明一个copy构造函数,一个copy assignment操作符和一个析构函数.此外如果你没有声明任何构造函数,编译器也会为你声明一个default构造函数.所有这些函数都是public且inline.因此,如果你写下:
class Empty{};
这就好像你写下这样的代码:
class Empty{
public:
Empty(){...}
Empty(const Empty & rhs){...}
~Empty() {...}
Empty & operator=(const Empty & rhs){...}
};
惟有当这些函数被需要,它们才会被编译器创建出来.程序中需要它们是很平常的事.下面代码造成上述每一个函数被编译器产生:
Empty el; //default构造函数
Empty e2(e1); //copy构造函数
e2 = e1; //copy assignment操作符
条款06:若不想使用编译器自动生成的函数,就该明确拒绝
class HomeForSale{...};
让HomeForSale对象拷贝动作以失败收场:
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); //企图拷贝h1--不该通过编译
h1 = h2; //企图拷贝h2--也不该通过编译
通常如果你不希望class支持某一特定机能,只要不声明对应函数就是了.但这个策略对copy构造函数和copy assignment操作符不起作用,条款05已经指出,如果你不声明它们,而某些人尝试调用它们,编译器会为你声明它们.
如果你不声明copy构造函数或copy assignment操作符,编译器为你产出一份,如果你声明它们,你的class还是支持copying.但这里的目标却是要阻止copying.
答案的关键是,所有编译器产出的函数都是public.为阻止这些函数被创建出来,你得自行声明它们,但这里并没有什么需求使你必须将它们声明为public.因此你可以将copy构造函数或copy assignment操作符声明为private.藉由明确声明一个成员函数,你阻止了编译器暗自创建其专属版本;而令这些函数为private,使你得以成功阻止人们调用它.
一般而言这个做法并不绝对安全,因为member函数和friend函数还是可以调用你的private函数.除非你够聪明,不去定义它们,那么如果某些人不慎调用任何一个,会获得一个连接错误."将成员函数声明为private而且故意不实现它们"这一伎俩是如此为大家接受,因而被用在C++ iostream程序库中阻止copying行为.将这个伎俩施行于HomeForSale也很简单:
class HomeForSale{
public:
...
private:
...
//只有声明
HomeForSale(const HomeForSale &);
HomeForSale & operator=(const HomeForSale &);
};
有了上述class定义,当客户企图拷贝HomeForSale对象,编译器会阻挠他.如果你不慎在member函数或friend函数之内那么做,轮到连接器发出抱怨.
将连接期错误移到编译期是可能的(而且那是好事,毕竟愈早侦测出错误愈好),只要将copy构造函数和copy assignamet操作符声明为private就可以办到,但不是在HomeForSale自身,而是在一个专门为了阻止copy动作而设计的base class内:
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable &);
Uncopyable & coperator=(const Uncopyable &);
};
class HomeForSale:private Uncopyable{
... //不再声明copy构造函数或copy assignment操作符
};
现在只要任何人(甚至是member函数或friend函数)尝试拷贝HomeForSale对象,编译器便试着生成一个copy构造函数和一个copy assignment操作符,而正如条款12所说,这些函数的"编译器生成版"会尝试调用其base class的对应兄弟,那些调用会被编译器拒绝,因为其base class的拷贝函数是private.
条款07:为多态基类声明virtual析构函数
class TimeKeeper{
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomickClock:public TimeKeeper{...}; //原子钟
class WaterClock:public TimeKeeper{...}; //水钟
class WristWatch:public TimeKeeper{...}; //腕表
设计factory函数,返回指针指向一个计时对象:
TimeKeeper * getTimeKeeper();
为遵守factory函数的规矩,被getTimeKeeper()返回的对象必须位于heap.因此为了避免泄漏内存和其他资源,将factory函数返回的每一个对象适当地delete掉很重要:
TimeKeeper * ptk = getTimeKeeper();
...
delete ptk;
问题来了,如果getTimeKeeper返回的指针指向一个derived class对象,而那个对象却经由一个base class指针被删除,而目前的base class(TimeKeeper)有个non-virtual析构函数.这时很可能derived class成分没被销毁,其析构函数也未能执行,而base class成分已被销毁,于是造成一个诡异的"局部销毁"对象.
消除这个问题的做法很简单:给base class一个virtual析构函数.
class TimeKeeper{
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
TimeKeeper * ptk = getTimeKeeper();
...
delete ptk; //现在,行为正确
如果class不含virtual函数,通常表示它并不意图被用作一个base class.当class不企图被当作base class,令其析构函数为virtual往往是个馊主意.
条款08:别让异常逃离析构函数
C++并不禁止析构函数吐出异常,但它不鼓励你这样做.考虑以下代码:
class Widget{
public:
...
~Widget(){...}//假设这个可能吐出一个异常
};
void doSomething(){
std::vector<Widget> v;
...
} //v在这里被自动销毁
当vector v被销毁,它有责任销毁其内含的所有的Widgets.假设v内含十个Widgets,而在析构函数第一个元素期间,有个异常被抛出.其他九个Widgets还是应该被销毁,因此v应该调用它们各个析构函数.但假设在那些调用期间,第二个Widget析构函数又抛出异常.现在有两个同时作用的异常,这对C++而言太多了.在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为.
举个例子,假设你使用一个class负责数据库连接:
class DBConnection{
public:
...
static DBConnection create();
void close();
};
为确保客户不忘记在DBConnection对象身上调用close(),一个合理的想法是创建一个用来管理DBConnection资源的class,并在其析构函数中调用close.
class DBConn{ //用来管理DBConnection对象
public:
...
~DBConn(){
db.close();
}
private:
DBConnection db;
};
这便允许客户写出这样的代码:
//开启一个区块(block)
//建立DBConnection对象并交给DBConn对象以便管理
//通过DBConn接口使用DBConnection对象
//在区块结束点,DBConn对象被销毁,因而自动为DBConnection对象调用close
{
DBConn dbc(DBConnection::create());
...
}
只要调用close成功,一切者美好.但如果该调用导致异常,DBConn析构函数会传播该异常,也就是允许它离开这个析构函数.
两个办法可以避免这一问题.DBConn的析构函数可以:
1 如果close抛出异常就结束程序
DBConn::~DBConn(){
try{
db.close();
}catch(...){
制作运转记录,记下对close的调用失败
std::abort();
}
}
2 吞下因调用close而发生的异常
DBConn::~DBConn(){
try{
db.close();
}catch(...){
制作运转记录,记下对close的调用失败
}
}
条款09:绝不在构造和析构过程中调用virtual函数
class Transaction{
public:
Transaction();
virtual void logTransaction() const = 0;
...
};
Transaction::Transaction(){
...
logTransaction();
}
class BuyTransaction:public Transaction{ //derived class
public:
virtual void logTransaction() const;
...
};
class SellTransaction:public Transaction{ //derived class
public:
virtual void logTransaction() const;
...
};
当以下行被执行时,会发生什么事:
BuyTransaction b;
无疑地会有一个BuyTransaction构造函数被调用,但首先Transaction构造函数一定会更早被调用.是的,derived class对象内的base class成分会在derived class自身成分被构造之前先构造妥当.Transaction构造函数最后一行调用virtual函数logTransaction,这时被调用的logTransaction是Transaction内的版本,不是BuyTransaction内的版本--即使目前即将建立的对象类型是BuyTransaction.
要解决这个问题,一种做法是在class Transaction内将logTransaction函数改为non-virtual,然后要求derived class构造函数传递必要信息给Transation构造函数:
class Transaction{
public:
explicit Transaction(const std::string & logInfo);
void logTransaction(const std::string & logInfo) const; //non-virtual函数
...
};
Transaction::Transaction(const std::string & logInfo){
...
logTransaction(logInfo);
}
class BuyTransaction:public Transaction{
public:
BuyTransaction(parameters):Transaction(createLogString(parameters)){
...
}
...
private:
static std::string createLogString(parameters);
};
换句话说由于你无法使用virtual函数从base classes向下调用,在构造期间,你可以藉由"令derived classes将必要的构造信息向上传递至base class构造函数"替换之而加以弥补.
0 0
原创粉丝点击