Effective C++(3)—— 资源管理

条款13:以对象管理资源(RAII)

class Investment { ... };

Investment* createInvestment();


void f() {
    Investment* pInv = createInvestment();
    ...
    delete pInv;
}

某些特殊情况,例如 ... 这个区域存在return语句,delete语句不会被执行到,造成内存泄漏。使用std::shared_ptr解决此问题。

void f() {
    std::shared_ptr<Investment> pInv1(createInvestment());
    std::shared_ptr<Investment> pInv2(pInv1);
    pInv1 = pInv2;
    ...
}

 shared_ptr在其析构函数内做delete而不是delete[]动作,对于array来说使用shared_ptr是个馊主意。

//馊主意,会用上错误的delete形式
std::shared_ptr<std::string> aps(new std::string[10]);

std::shared_ptr<int> spi(new int[1024]);

请注意:

获得资源后立刻放进管理对象;

管理对象运用析构函数确保资源被释放;

为了防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源;

使用shred_ptr一般是较佳选择。

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

 假如我们使用C API函数处理类型为Mutex的互斥锁对象,有lock和unlock函数可用:

void lock(Mutex* pm);
void unlock(Mutex *pm);

创建一个class用来管理锁

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

private:
    Mutex *mutexPtr;
};

客户调用

Mutex m;
...
{
    Lock m1(&m);
}

这很好,但如果Lock对象被复制,会发生什么?

Lock ml1(&m);
Lock ml2(ml1);

大多数情况,会选择以下两种可能:

  • 禁止复制。将copying操作声明为private。
class Uncopyable {
protected:
    Uncopyable() {}
    ~Uncopyable() {}
private:
    Uncopyable(const Uncopyable &);
    Uncopyable& operator=(const Uncopyable &);
};

class Lock : private Uncopyable {
public:
    ...
};
  • 对底层资源祭出“引用计数法”。不再声明析构函数,std::shared_ptr在引用计数为0时自动调用shared_ptr的删除器。
class Lock {
public:
    explicit Lock(Mutex *pm) : mutexPtr(pm, unlock) {
        lock(mutexPtr.get());
    }

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

 请记住:

复制RAII对象必须一并复制它所管理的资源;

普遍而常见的RAII class copying行为是:抑制copying、实施引用计数法。

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

std::shared_ptr<Investment> pInv(createInvestment());

int daysHeld(const Investment* pi);

int days = daysHeld(pInv);    //无法通过编译

 daysHeld需要时Investment*指针,传给它的确是一个std::shared_ptr<Investment>的对象。可以用两种方法达成转换:显式转换和隐式转换。

// C API
FontHandle getFont();
void releaseFont(FontHanle fh);

class Font {
public:
    explicit Font(FontHandle fh) : f(fh) { }
    ~Font() { releaseFont(f); }
    FontHandle get() const { return f; } //显示转换函数
private:
    FontHandle f;
};

void changeFontSize(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize);   //调用显示转换

 频繁调用时到处要求显示转换,使人们倒尽胃口。另一个方法是提供隐式转换函数。

class Font {
public:
    ...
    operator FontHandle() const { return f;}   //隐式转换函数
    ...
};

Font f(getFont());
int newFontSize;
...
chageFontSize(f, newFontSize);         //调用隐式转换

这个隐式转换增加错误发生机会。如客户可能会在需要Font的时候意外创建了一个FontHandle:

Font f1(getFont());
...
FontHandle f2 = f1; //原意是要拷贝一个Font,现在却将f1隐式转换成FontHandle后复制它

 上面程序有个FontHandle由Font对象f1管理,但那个FontHandle也可以通过直接使用f2获得。当f1被销毁时,字体被释放,而f2因此成为野指针。

请记住:

APIs往往是访问原始资源 ,所以每一个RAII class应该提供一个“获取其所管理之资源”的办法。

对原始资源的访问可能经由显示转换或隐式转换,一般而言显式转换比较安全,但是隐式转换对客户比较方便。

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

std::string* stringArray = new std::string[100];
...
delete stringArray;

当使用new的时候,有两个事情会发生。第一,内存被分配出来;第二,针对此内存会有一个(或更多)的构造函数被调用。当使用delete的时候,也有两件事发生。第一,针对此内存的一个(或更多)的析构函数被调用;第二,内存被释放。delete的最大问题在于:即将被删除的内存之内究竟有多少对象?换句话说就是即将被删除的指针是单一对象还是对象数组。

std::string* stringPtr1 = new std::string;
std::string* stringPtr2 = new std::string[100];
...
delete stringPtr1;
delete []stringPtr2;

 请记住:

如果在new表达式中使用[],必须在相应的delete表达式中使用[]。

如果在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。

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

假设有两个函数

//处理程序的优先权
int priority();

//在动态分配的Widget上进行带有优先权的处理   
void processWidget(std::shared_ptr<Widget> pw, int priority);

processWidget(new Widget, priority());   
//无法通过编译,参数需要shared_ptr对象, Widget构造函数是个expliclit的,无法进行隐式转换

processWidget(std::shared_ptr<Widget> (new Widget), priority());

于是在调用processWidget函数之前,编译器必须创建代码,做一下三件事:调用priority函数,执行new Widget,调用std::shared_ptr构造函数。C++编译器以什么样的次序完成这几件事呢?可以肯定的是new Widget一定执行于std::shared_ptr构造函数前,但priority函数则可以排在第二或第三执行。如果编译器选择以第二顺位执行它,万一priority函数发生异常,new Widget返回的指针将会遗失,因为它尚未被置入std::shared_ptr内,可能引发内存泄漏。通常解决办法如下:

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

请记住:

以独立的语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦有异常被抛出,有可能导致难以察觉的资源泄漏。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值