您的位置:首页 > 编程语言 > C语言/C++

effective c++ 笔记 (13-17)

2015-03-30 22:23 281 查看
//---------------------------15/03/30----------------------------

//#13 以对象管理资源
{
void f()
{
Investment *pInv = createInvestment();
...
delete pInv;
}

/*

1:这里很容易出现内存泄漏:

1>...中过早地return。

2>...中抛出异常。

2:为了确保pInv总是被释放,我们需要将资源放进对象内,当控制流离开f时,

这个对象会自动调用析构函数释放掉资源。

3:这里有两个常用的智能指针可以实现:

1>std::auto_ptr。使用方法:std::auto_ptr<Investment> pInv(createInvestment());

记得不要让多个auto_ptr同时指向一个对象,这样会造成多次delete。为了预防这个问题,auto_ptr

在被copy给别的指针时,会变成null。

2>tr1::shared_ptr。这个智能指针使用引用计数,在被copy一次后会自动计数加1,调用一次析构函数

会自动计数减1,当计数为0时销毁对象。

唯一的问题是循环引用。两个没有被使用的对象相互指着对方,这样看起来好像都在使用中,然而并不是这样。

4:如果要对申请到的数组资源使用智能指针,可以使用boost::scoped_array和boost::shared_array

这两个在析构时调用delete[];

*/
}

//#14 在资源管理类中小心coping行为
{

// auto_ptr和tr1::shared_ptr不适合掌管非heap-based资源。这时候需要自己建立资源管理类
class Lock
{

public:
explicit Lock(Mutex* pm): mutexPtr(pm)
{
lock(mutexPtr);
}
~Lock()
{
unlock(mutexPtr);
}

private:
Mutex *mutexPtr;
};



/* 这时会出现一个问题:如果对象被复制,这个锁会被再锁上一次。如果是在同一线程中这么做

程序就死锁了,无法再恢复。所以大多数时候,可以选择以下两种处理。

1>禁止复制。像Lock这样的class,这样处理是科学的。可以从Uncopyable那用私有继承。

2>对底层资源使用“引用计数法”。Mutex的“引用计数”版本可以使用tr1::shared_ptr,并

指定“删除器”,当引用次数为0时,“删除器”会被调用。代码看起来像这样:
*/
class Lock
{

public:
explicit Lock(Mutex* pm):mutexPtr(pm, unlock)
{
Lock(mutexPtr.get());
}

private:
std::tr1::shared_ptr<Mutex> mutexPtr;
};

/* 有时候会有两种特殊的需求:

1>复制底部资源(深拷贝)。这是为了当你不需要某个复件时确保它被释放,所以拷贝时要连同

指针指向的资源一起拷贝,不然原始对象删除后就没法用了。

2>转移底部资源的拥有权。就像auto_ptr一样。



*/
}

//#15: 在资源管理类中提供对原始资源的访问
{

/* 1:在资源管理类中,常常需要提供对原始资源的访问,原因是:一些api需要的就是原始资源

如果传入一个资源管理类,它们并不起作用。

2:提供对原始资源访问的方法有二:

1>提供显示访问: */
class Font
{

public:
explicit Font(FontHandle fh): f(fh)
{}
~Font() { releaseFont(f);}
FontHandle get()const {return f;}

private:
FontHandle f;
};

/* 这样一来就可以通过调用get()来访问原始资源了,但是每次都要用f.get(),显得十分麻烦。

2>提供隐式转换函数:

operator FontHandle() const {return f;}

但是这么做,存在风险,客户有时候笔误就会造成获得一个错误的类型: */
Font f1(getFont());
...
FontHandle f2 = f1;//本来客户是想要 Font f2 = f1;

/* 但是就算这么调用了,客户也看不出来,因为隐式转换的关系,表达式是可以编译通过的。

而且使用起来也是一样的。最后,当f1被销毁时,问题来了,f2就成为“虚吊的”,就是指向

的资源以及被释放。

3:两种设计各有优势,为了安全倾向显示转换,为了方便倾向隐式转换。

4:RAII class返回原始资源与封装发生矛盾,这是真的。但是并没有关系,RAIIclass是为了确保

资源一定会被释放,而不是为了封装。

*/
}



//#16 成对使用new和delete时要采用相同形式
{

/* 1:当使用new时,有两件事情发生:

1>内存被分配出来。

2>针对此内存会调用构造函数(一个或多个)。

2:当使用delete时,也有两件事发生:

1>先调用析构函数(一个或多个)

2>释放内存。

3:数组对象的内存中一般还包含一个数组大小的记录,以便delete知道需要调用多少次析构函数

所以在调用时必须成对出现,不然会发生不可预测的情况。

4:使用typedef时,最好不要对数组进行typedef。

比如: typedef std::string AddressLines[4];

这样很容易错误地使用delete,所以应该避免这种定义。 */
}



//#17 以独立的语句将newed对象置入智能指针
{

// 看一个函数调用:
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());

/* 这样调用有可能造成内存泄漏,首先,编译器为了效率问题执行以下顺序:

1>new widget;

2>priority();

3>tr1::shared_ptr的构造函数

如果priority()的调用导致异常,那么new Widget返回的指针就遗失了,内存就泄漏了。

所以为了避免着问题很简单: */
std::tr1::shared_ptr<Widget> pw(new Widget);


processWidget(pw, priority());

// 把newed对象置入智能指针的操作用独立语句来实现,这样就不会产生内存泄漏了。
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: