前几章的笔记多有不足,这一章会持续改进
考虑以下易出错的例子:
class Investment { ... }; //投资类型继承体系中的root类
//工厂函数,指向Investment继承体系内的动态分配对象,参数省略
Investment* createInvestment {};
void f()
{
Investment* pInv = createInvestment(); //调用工厂函数
... //若这里return则无法执行delete
delete pInv; //释放pInv所指对象
}
解决方案:把资源放进对象,可利用析构函数自动调用机制确保资源释放
以对象管理资源的两个关键想法:
auto_ptr:
auto_ptr在C++11中已被弃用,以下简要介绍
void f()
{
std::auto_ptr<Investment> pInv(createInvestment());
...
} //auto_ptr的析构函数自动删除pInv
std::auto_ptr<Investment< pInv1(createInvestment());
std::auto_ptr<Investment< pInv2(pInv1); //现在pInv2指向对象,pInv1为null
pInv1 = pInv2; //现在pInv1指向对象,pInv2为null
shared_ptr:属于引用计数型智慧指针(reference-counting smart pointer,RCSP)
void f()
{
std::tr1::shared_ptr<Investment> pInv(createInvestment());
...
} //shared_ptr的析构函数自动删除pInv
void f()
{
...
std::tr1::shared_ptr<Investment> pInv1(createInvestment());
std::tr1::shared_ptr<Investment> pInv2(pInv1); //指向同一个对象
pInv1 = pInv2; //同上
...
} //pInv1和pInv2被销毁,他们所指的对象也被销毁
auto_ptr和shared_ptr的析构函数做delete而非delete[],故不适合在动态分配而得的array身上使用,即使能通过编译
Boost中boost::scoped_array和boost::shared_array则可用于数组且类似auto_ptr和shared_ptr
std::auto_ptr<std::string> aps(new std::string[10]); //会调用错误形式的delete
std::tr1::shared_ptr<int> spi(new int[1024]); //同上
Tips:
对mutex一点不了解emmm,硬着头皮总结下
非heap-based的资源不适合使用智能指针作为资源掌管者(resource handlers)
考虑使用C API函数处理类型为Mutex的互斥器对象(mutex objects),共有lock和unlock两函数可用。为确保不会忘记把被锁的Mutex解锁,可建立类以管理机锁,该类的结构符合RAII守则
void lock(Mutex* pm); //锁定pm所指的互斥器
void unlock(Mutex* pm); //解除互斥器的锁定
//管理机锁的类,符合RAII守则
class Lock {
public:
explicit Lock(Mutex* pm)
: mutexPrt(pm)
{ lock(mutexPtr); }
~Lock() { unlock(mutexPtr); }
private:
Mutex *mutexPtr;
};
//客户对Lock的用法符合RAII方式
Mutex m;
...
{
Lock ml(&m);
...
}
如果要复制Lock对象,则可能:
Lock m11(&m); //锁定m
Lock m12(m11); //将m11复制到m12上
class Lock {
public:
explicit Lock(Mutex* pm) //以Mutex初始化shared_ptr
: mutexPtr(pm, unlock) //以unlock函数作为删除器
{
lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr; //使用shared_ptr替换raw pointer
};
Tips:
由于auto_ptr已弃用,本条款不整理和其相关的内容
有时智能指针不能直接使用(如下例子),需要显示转换或隐式转换:
class Investment {
public:
bool isTaxFree() const;
...
}
Investment* createInvestment(); //工厂函数
std::tr1::shared_ptr<Investment> pInv(createInvestment());
int daysHeld(const Investment* pi); //返回投资天数
int days = daysHeld(pInv); //错误!daysHeld需要Investment*而非tr1::shared_ptr
//显示转换
int days = daysHeld(pInv.get());
//隐式转换,tr1::share_ptr重载了指针取值(pointer dereferencing)操作符(->和*)
bool taxable1 = !(pInv->isTaxFree());
bool taxable2 = !((*pInv).isTaxFree());
有时必须取得RAII对象内的原始资源,考虑用于字体的RAII类:
FontHandle getFont(); //这是C API,省略参数
void releaseFont(FontHandle fh); //来自同一组
class Font { //RAII类
public:
explicit Font(FontHandle fh) //获得资源
: f(fh) //使用pass-by-value,因为C API这样做
{ }
~Font() { releaseFont(f); } //释放资源
private:
FontHandle f; //原始字体资源
};
class Font {
public:
...
FontHandle get() const { return f; } //显式转换函数
...
};
void changeFontSIze(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize);
class Font { //RAII类
public:
...
operator FontHandle() const { return f; } //隐式转换函数
...
};
changeFontSize(f, newFontSize)
FontHanle f0 = f; //想要拷贝,但是将f1隐式转换为其底部的FontHandle才复制
Tips:
std::string* stringArray = new std::string[100];
...
delete stringArray
以上程序的行为不明确:
std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1; //删除对象
delete [ ] stringPtr2; //删除对象组成的数组
使用typedef需要考虑相同的问题(下例中数组使用typedef并不合适,容易产生错误,仅做说明):
typedef std::string AddressLines[4]; //地址有4行,每行一个string
std::string* pal = new AddressLines; //本质上同new string[4]
delete pal; //错误!行为未有定义
delete [ ] pal; //正确
Tips:
考虑涉及优先权的例子:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
//错误!new Widget无法隐式转换为shared_ptr,因为shared_ptr的构造函数为explicit
processWidget(new Widget, priority());
//可以通过编译,但可能泄漏资源
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());
编译器产出processWidget调用码之前,必须首先核酸即将被传递的各个实参,其要做事情有三件:
实际执行的次序弹性很大,只能确定执行new Widget一定先于调用tr1::shared_ptr构造函数,但调用priority的次序不一定。若编译器选择以下次序:
则如果调用priority出现异常,那new Widget返回的指针将遗失,其未来得及放入tr1::shared_ptr内,进而导致资源泄漏。即创建资源和资源转换为资源管理对象直接有可能发生异常干扰
解决方法:使用分离语句,因为编译器对于跨越语句的各项操作没有重新排列的自由
std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority()); //这样调用不会泄漏
Tips: