缠丝猫 发表于 2024-5-18 21:31:54

《Effective C++》第三版-3. 资源管理(Resource Management)

目录

[*]条款13:以对象管理资源(Use objects to manage resources)

[*]关键想法
[*]智能指针

[*]条款14:在资源管理类中鉴戒copying举动(Think carefully about copying behavior in resource-managing classes)
[*]条款15:在资源管理类中替工对原始资源的访问(Provide access to raw resources in resource-managing classes)

[*]表现转换或隐式转换
[*]优缺点

[*]条款16:成对使用new和delete时要采取相同形式(Use the same form in corresponding uses of new and delete)
[*]条款17:以独立语句奖newed对象置入智能指针(Store newed objects in smart pointers in standalone statements)

前几章的条记多有不敷,这一章会持续改进
条款13:以对象管理资源(Use objects to manage resources)

关键想法

考虑以下易出错的例子:
class Investment { ... };//投资类型继承体系中的root类
//工厂函数,指向Investment继承体系内的动态分配对象,参数省略
Investment* createInvestment {};
void f()
{
        Investment* pInv = createInvestment();//调用工厂函数
        ...//若这里return则无法执行delete
        delete pInv;//释放pInv所指对象
}办理方案:把资源放进对象,可利用析构函数自动调用机制确保资源释放
以对象管理资源的两个关键想法:

[*]获得资源后立刻放进管理对象(managing object)内

[*]资源取得机会便是初始化机会(Resource Acquisition Is Initialization,RAII)
[*]偶然获得的资源会拿来赋值而非初始化

[*]管理对象运用析构函数确保资源释放

[*]岂论控制流怎样离开区块,一旦对象被烧毁(如离开对象作用域)其析构函数会自动调用

智能指针

auto_ptr:

[*]通过copy构造函数或copy assignment操纵符复制它们,它们会变成null,复制所得的指针将取得资源的唯一全部权
[*]故需要元素能够复制地STL容器不兼容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优缺点

偶然必须取得RAII对象内的原始资源,考虑用于字体的RAII类:
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被销毁,他们所指的对象也被销毁

[*]表现转换:可读性强,但是需要API时必须调用get
std::auto_ptr<std::string> aps(new std::string);//会调用错误形式的delete
std::tr1::shared_ptr<int> spi(new int);//同上

[*]隐式转换:调用C API更自然,但是易出错

[*]可能在需要Font时不测创建FontHandle,且f被烧毁则f0成为悬空的(dangle)

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);
        ...
}Tips:

[*]API往往要求访问原始资源,故RAII类应提供取得其管理的资源的方法
[*]对原始资源的访问包含表现转换和隐式转换,一般表现转换安全而隐式转换方便
条款16:成对使用new和delete时要采取相同形式(Use the same form in corresponding uses of new and delete)

Lock m11(&m);//锁定m
Lock m12(m11);//将m11复制到m12上以上程序的举动不明确:

[*]使用new时会发生两件事:

[*]内存分配
[*]调用针对此内存的构造函数

[*]delete需要知道被删除的内存内有多少对象,其决定了要调用多少析构函数

[*]若指针指向数组对象,则数组所用的内存包含数组大小信息
[*]若指针指向单一对象,则无上述信息
[*]需要人为告诉delete所删除的对象类型

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
};使用typedef需要考虑相同的问题(下例中数组使用typedef并不合适,容易产生错误,仅做说明):
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());Tips:

[*]如果new加上[],则delete必须加上[];如果new没加[],则delete不能加[]
条款17:以独立语句奖newed对象置入智能指针(Store newed objects in smart pointers in standalone statements)

考虑涉及优先权的例子:
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;//原始字体资源
};编译器产出processWidget调用码之前,必须首先核酸即将被传递的各个实参,其要办事情有三件:

[*]第一实参

[*]执行new Widget
[*]调用tr1::shared_ptr构造函数

[*]第二实参

[*]调用priority

现实执行的序次弹性很大,只能确定执行new Widget肯定先于调用tr1::shared_ptr构造函数,但调用priority的序次不愿定。若编译器选择以下序次:

[*]执行new Widget
[*]调用priority
[*]调用tr1::shared_ptr构造函数
则如果调用priority出现异常,那new Widget返回的指针将遗失,其将来得及放入tr1::shared_ptr内,进而导致资源走漏。即创建资源和资源转换为资源管理对象直接有可能发生异常干扰
办理方法:使用分离语句,因为编译器对于跨越语句的各项操纵没有重新排列的自由
class Font {
public:
        ...
        FontHandle get() const { return f; }//显式转换函数
        ...
};

void changeFontSIze(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize);Tips:

[*]以独立语句将newed对象存储于智能指针内,否则一旦有异常则可能发生隐秘的资源走漏

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 《Effective C++》第三版-3. 资源管理(Resource Management)