Effective C++ 之《资源管理》

  • 条款13:以对象管理资源
  • 条款14:在资源管理类中小心copying行为
  • 条款15:在资源管理类中提供原始资源的访问
  • 条款16:成对使用new和delete时要采取相同形式
  • 条款17:以独立语句将newed对象置入智能指针

条款13:以对象管理资源

  看如下一个例子:

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

  如上,在发f()函数的结尾释放了pInv指针所指向的内存。但是这样的写法是有风险的。如果…里面的内容提前返回或者出现异常,那么这块内存将不能合法的释放掉。这样就会造成资源泄漏的问题。

  那么该条款主要说明的是,一对象管理资源,也就是让释放的动作放在类的析构函数中来进行,这样在f()函数的作用域结束的时候,可以自动将内存释放掉。

  这里主要使用的是C++标准库中的智能指针来管理内存资源。

void f() {
	std::auto_ptr<Investment> pInv(createInvestment());
}

  使用std::auto_ptr来管理资源,这里提出了两个关键想法:

  1. 获得资源后立刻放进管理对象内。
  2. 管理对象运用析构函数确保资源被释放。

  也就是说,在createInvestment()函数被调用后,获取的资源需要立即放入到std::auto_ptr资源管理对象中,同时在执行函数的作用域结束的时候,调用资源管理对象的析构函数来释放资源。

  std::auto_ptr不能与其他被复制的指针拥有同一个对象,也就是如果出现:

std::auto_ptr<Investment> pInv1 = pInv;

  那么pInv所指向的对象将未null。当然std也提供的共享指针:std:shared_ptr。也就是如果使用std:shared_ptr,那么pInv1和pInv所指向的对象是同一个内存。

  在实际开发中,我们经常会使用std:shared_ptr,但本人不是太喜欢使用共享智能指针。有时候如果我们要控制释放资源的时序或这时机时,共享智能指针显然是不合适的。所以使用它也是视情况而定,它的出现减少的程序员对内存管理的工作。但是在自己管理资源时,会遵循RAII原则。

总结

为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
两个常被使用的RAII classes分别是std::shared_ptr和auto_ptr。前者通常是最佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null。

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

  在条款13中有说到本人不喜欢使用智能指针,那么如果是自己管理类对象的时候,也要自定义RAII类,来对资源进行管理。那么自定义RAII类需要主要什么呢?

  条款14说明,在自定义RAII类的时候需要小心copying行为,因为如果不对其采取措施的话,会出现野指针的使用或者一些其他不明确的行为发生。

  所以一般采取的措施如下:

  1. 禁止复制。
  2. 对底层资源祭出“引用计数法”(reference-count)。类似std:shared_ptr。
  3. 复制底部资源。(对内部的所以资源进行深拷贝)
  4. 转移底部资源的拥有权。类似std::auto_ptr

  其中1方法使用的比较多,通常在开发过程中,很多业务类是不需要被复制的,所以干脆禁止其复制就可以了。同样的3也会经常使用,这适用于一些小类,容易复制的简单类,大多是数据协议类。如果出现2和4的情况,可以直接使用std:shared_ptr或者std::auto_ptr,这样大大减少了开发量。

总结

复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
普通而常见的RAII class copying行为是:抑制copying、施行引用计数法。不过其他行为也都可能被实现。

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

  使用过shared_ptr应该都清楚,它提供了一个get方法用于客户获取它的原始指针。即提供了客户对原始资源的访问。

std::shared_ptr<font> ptr = std::make_shared<font>();
font* p = ptr.get();

  该条款告诉我们的是对于RAII类我们需要对其提供一个原始资源的访问方式。显式或者隐式访问都可以。因为有时候会出现这种情况:

int daysHeld(const Investment* pi);

如上的方法,他需要的形参就是原始指针的类型,那么如果使用智能指针shared_ptr作为实参进行传递是不行的。所以标准库为我们提供了get方法用于获取原始资源的访问权。
总结

APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的办法。
对原始资源的访问可能经由显示转换或隐式转换,一般而言显示转换比较安全,但是隐式转换对客户比较方便。

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

  该条款比较简单,它要求我们在使用new和delete时要采取相同的形式。如果出现如下的情况,那么会出现未定义的问题:

std::string *p = new std::string;
delete []p;
std::string *p1[2] = new std::string[2];
delete p1;

  如上,如果只是申请了一个对象,但是使用delete[],就会出现程序多次进行析构。如果申请的是数组对象,而使用的delete,那么程序可能只释放了一次内存,调用了一次析构函数而已。
总结

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

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

  考虑如下的情况:

processWidget(std::shared_ptr<widget> pw, int priority);
processWidget(std::shared::ptr<widget>(new widget), priority());

  如上的调用可能会存在内存泄漏的风险。我们知道程序执行的顺序:

  1. new widget
  2. 调用std::shared::ptr的构造函数。
      但是这个函数由第二个参数,所以程序执行的顺序可能是这样的:
  3. new widget
  4. priority()
    3.调用std::shared::ptr的构造函数

  也就是 priority()的调用插在了中间,那么当 priority()出现异常的话,new widget的对象将无法捕捉到,这会导致我们无法使用std::shared::ptr对该内存进行管理。从而导致内存泄漏。
  那么正确的做法是什么样的呢:

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

  这样保证了程序的执行顺序,从而防止出现内存泄漏的风险。

总结

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

你可能感兴趣的:(Effective,C++,C/C++)