Effective C++笔记:3.资源管理

条款13:以对象管理资源

class Investment{...}

Investment* createInvestment();  //返回指针,指向Investment继承体系内的动态分配单元
void f()
{
    Investment* pInv=createInvestment();   //调用工厂函数
    ...    //此处有return或者异常时,过早退出无法delete
    delete pInv;                           //释放pInv所指对象
}

把资源放入对象内,便可依赖C++的析构函数自动调用机制确保资源被释放

许多资源被动态分配与堆中,而后被用于单一区块或函数内,他们应该在控制流离开那个区块或函数时被释放

auto_ptr:析构函数自动对其所指对象调用delete

               注意:别让多个auto_ptr指向同一对象

void f()
{
    std::auto_ptr pInv(createInvestment());   
    ...    
}

若通过复制构造函数或复制操作符复制它们,会变成null,复制所得的指针将取得资源唯一拥有权

std::auto_ptr pInv1(createInvestment()); //pInv1指向createInvestment返回物
std::auto_ptr pInv2(pInv1); //pInv2指向对象 pInv1设为null
pInv1=pInv2;  //pInv1指向对象 pInv2设为null

tr1::shared_ptr:持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源

void f()
{
    std::tr1::shared_ptr pInv1(createInvestment()); 
                                                //pInv1指向createInvestment返回物
    std::tr1::shared_ptr pInv2(pInv1);//pInv1和pInv2指向同一个
    pInv1=pInv2;                     //无任何改变
    ...    
}         //pInv1和pInv2被销毁,所指对象也被销毁

资源取得时机便是初始化时机(RAII)

条款14:在资源管理类中小心复制行为

class Lock{
public:
    explicit Lock(Mutex* pm):mutexPtr(pm)
    {
        lock(mutexPtr);    //获得资源
    }
    ~lock()
    {
        unlock(mutexPtr);  //释放资源
    }
private:
    Mutex *mutexPtr;
};

问题:当一个RAII对象被复制时会发生什么事??

解决:

(1)禁止复制。将复制操作声明为private(条款06)

class Lock:private Uncopyable{   //禁止复制
public:
    ...
};

(2)对底层资源使用“引用计数法”

只要内含一个tr1::shared_ptr成员变量,RAII class可以实现“引用计数”复制行为

tr1::shared_ptr指定“删除器”,当引用次数为0时便调用

class Lock {   
public:
    explicit Lock(Mutex* pm)     // 以某个Mutex初始化shared_ptr
     :mutexPtr(pm, unlock)       // 并以unlock函数为删除器
    {
        lock(mutexPtr.get());
    }
private:
    std::tr1::shared_ptr mutexPtr;  // 使用shared_ptr替换raw point
};

(3)复制底部资源

复制资源管理对象时,进行的是“深度拷贝”

“指向堆内存”的指针构成的字符串对象被复制,无论指针或是其所指向内存都会被制作出一个复制

(4)转移底部资源的拥有权

RAII对象被复制,永远只有一个RAII对象指向一个未加工资源,此时,资源拥有权会从被复制物转移到目标物

  • 复制RAII对象必须一并复制它所管理的资源,资源的copy行为决定RAII对象的copy行为
  • 普通的RAII类的复制行为是:抑制复制,施行引用计数法

条款15:在资源管理类中提供对原始资源的访问

std::tr1::shared_ptr pInv(createInvestment());
// 某个函数处理Investment对象:
int daysHeld(const Investment* pi);  // 返回投资天数 

将RAII对象转化为所含的原始资源:

(1)显式转换

get()成员函数:返回智能指针内部原始指针:

int days = daysHeld(pInv.get());

tr1::shared_ptr和auto_ptr也重载了指针取值操作符(operator->和operator*),允许隐式转换至底部原始指针:

class Investment {
public:
    bool isTaxFree() const;
    ...
};

Investment* createInvestment();  // factory函数

std::tr1::shared_ptr pi1(createInvestment());  // std::tr1::shared_ptr统一管理一笔资源

bool taxable1 = !(pi1->isTaxFree());   // 经由operator->访问资源
...
std::auto_ptr pi2(createInvestment());  // std::auto_ptr统一管理一笔资源

bool taxable2 = !((*pi2).isTaxFree());   // 经由operator*访问资源
...

(2)隐式转换

FontHandle getFont();   
void releaseFont(FontHandle fh);

class Font {
public:
    explicit Font(FontHandle fh):f(fh)
    { }
    ~Font()
    { releaseFont(f);}
private:
    FontHandle f;
};

假设处理的是FontHandle,将Font转换为FontHandle会是一种很繁琐的要求,Font class可为此提供一个显式的转换函数,像get那样:

class Font {
public:
    ...
    FontHandle get() const { return f; }
    ...
};

...
Font f(getFont());
changeFontSize(f.get(),newFontSize); // Font转化为FontHandle

另一种方法是令Font提供隐式转换函数:

class Font {
public:
    ...
    opeartor FontHandle const 
    { return f; }
    ...
};

...
Font f(getFont());
changeFontSize(f,newFontSize); // Font转化为FontHandle

显式转换比较安全,但隐式转换对客户比较方便

条款16:成对使用new和delete时要采取相同形式

使用new,会有两件事发生:

(1)内存被分配出来(通过operator new的函数)

(2)针对此内存会有一个构造函数被调用

使用delete,会有两件事发生:

(1)针对此内存会有一个构造函数被调用

(2)内存通过operator delete的函数被释放

使用delete时加上[ ],delete认定指针指向一个数组,否则指向单一对象

std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1;       // 删除一个对象
delete [] stringPtr2;    // 删除一个由对象组成的数组
  • 在new表达式中使用[ ],必须在响应的delete表达式中也使用[ ]

条款17:以独立语句将new ed对象置入智能指针

int priority();
void processWidget(std::tr1::shared_ptr pw, int priority);

调用processWidget():

processWidget(Widget, priority());  // error: 不能通过编译

写成这样可以通过编译:

processWidget(std::tr1::shared_ptr(new Widget), priority());  // 可能造成内存泄漏

在调用processWidget之前,编译器创建代码,做三件事:

(1)调用priority

(2)执行new Widget

(3)调用std::tr1::shared_ptr构造函数

但调用次序不定,若先调用priority,导致异常,则new Widget返回指针遗失

避免这类问题的方法:分离语句

分别写出(1)创建Widget;(2)将它置入一个智能指针内;(3)把智能指针传给processWidget

std::tr1::shared_ptr  pw(new Widget)
processWidget(pw, priority());  

 

你可能感兴趣的:(effective,c++)