new

new运算符意味着在堆上分配内存空间,并将这块内存的管理权(责任)交给用户,意味着我们需要手动释放堆上的内存。

异常安全的类

异常安全简单而言就是:当你的类抛出异常后,你的程序会不会爆掉。爆掉的情况主要包括:内存泄漏,以及不一致的类状态(例如一个字符串类,它的size()方法返回的字符串大小与实际的字符串大小不同),这里仅讨论内存泄漏的情况。

  1. // RAII 风格的类
  2. class TooSimple {
  3. private:
  4. Resource *a;
  5. Resource *b;
  6. public
  7. TooSimple() {
  8. a = new Resource();
  9. b = new Resource(); //在这里抛出异常
  10. }
  11. ~TooSimple() {
  12. delete a;
  13. delete b;
  14. }
  15. };

一个函数(或方法)抛出异常,那么它首先将当前栈上的变量全部清空(unwinding),如果变量是类对象的话,将调用其析构函数,接着,异常来到call stack的上一层,做相同操作,直到遇到catch语句。

b = new Resource() 句抛出异常,那么构造函数将被强行终止,指针a,b从call stack上清除,由于此时构造函数还未完成,所以TooSimple的析构函数也不会被调用,a已经被分配了资源,但是call stack被清空,地址已经找不到了,于是delete永远无法执行,于是内存泄漏发生了。

  1. template <typename T>
  2. class StupidPointer {
  3. public:
  4. T *ptr;
  5. StupidPointer(T *p) : ptr(p) {}
  6. ~StupidPointer() { delete ptr; }
  7. };
  8. TooSimple() {
  9. a = StupidPointer<Resource>(new Resource());
  10. b = StupidPointer<Resource>(new Resource());
  11. };

此时的a,已经不再是指针,而是StupidPointer类,在清空call stack时,它的析构函数被调用,于是a指向的资源被释放了。但是,StupidPointer类有一个严重的问题:当多个StupidPointer对象管理同一个指针时,一个对象析构后,剩下对象中保存的指针将变成指向无效内存地址的”野指针”(因为已经被delete过了啊)。

C++11的标准库提供了两种解决问题的思路:1、不允许多个对象管理一个指针(unique_ptr);2、允许多个对象管理同一个指针,但仅当管理这个指针的最后一个对象析构时才调用delete(shared_ptr)。这两个思路的共同点是:只允许delete一次。

C++11结合 RAII ,采用代理模式的思想,设计了三个智能指针,分别为 std::unique_ptr,std::shared_ptr,std::weak_ptr。

求值顺序

  1. f(new A, new B); // 内存泄露
  2. f(shared_ptr<A>(new A), shared_ptr<B>(new B)); // 同样有内存泄露的风险

求值任何表达式的任何部分,包括求值函数参数的顺序都是未说明的。编译器能以任何顺序求值任何操作数和其他子表达式,并且可以在再次求值同一表达式时选择另一顺序。

std::unique_ptr