防止在构造函数中引发异常时防止内存泄漏的最佳实践是什么? [英] What's the best practice to prevent memory leak if an exception thrown in constructor?

查看:57
本文介绍了防止在构造函数中引发异常时防止内存泄漏的最佳实践是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道如果构造函数中引发了异常,则不会调用析构函数(简单类,没有继承).因此,如果在构造函数中引发异常,并且有可能无法清除一些堆内存.那么,这里的最佳实践是什么?假设我必须在构造函数中调用某些函数,并且它可能会引发异常.在这种情况下,我应始终使用共享指针吗?有什么选择?谢谢!

I know if an exception is thrown in constructor, destructor will not be called(simple class, no inheritance). So if an exception is thrown in constructor and there is a chance some heap memory is not cleaned up. So what's best practice here? let's assume I have to call some function in constructor and it may throw exception. Shall I always use shared pointer in this case? What's the alternatives? Thank you!

推荐答案

我会坚持使用 RAII 成语.

如果避免使用裸露"资源(例如,运算符new,裸露的指针,裸露的互斥体等),而是将所有内容包装到具有正确RAII行为的容器或类中,即使存在,您也不会遇到描述的问题的例外.

If you avoid "naked" resources (like operator new, naked pointers, naked mutexes, etc.) and instead wrap everything into a container or class with proper RAII behavior you will not have the problems you describe, even in the presence of exceptions.

也就是说,不要在构造函数中获取裸资源.而是创建一个对象实例,该对象本身遵循RAII.这样,即使您的构造函数失败(也就是创建实例的实例),也会调用已初始化对象的析构函数.

That is, do not acquire naked resources in your constructor. Instead, create an instance of an object which itself follows RAII. That way, even if your constructor fails (that is, the one which creates the instances), the destructors of the objects which were initialized will be called.

所以,这是不好的做法:

So, this is bad practice:

#include<iostream>
#include<stdexcept>

struct Bad {
  Bad() {
    double *x = new double;
    throw(std::runtime_error("the exception was thrown"));
  }

  ~Bad() {
    delete x;
    std::cout<<"My destructor was called"<<std::endl;
  }

  double *x;  
};

int main() {
  try {
    Bad bad;
  } catch (const std::exception &e) {
    std::cout<<"We have a leak! Let's keep going!"<<std::endl;
  }
  std::cout<<"Here I am... with a leak..."<<std::endl;
  return 0;
}

输出:

We have a leak! Let's keep going!
Here I am... with a leak...

与这个人为的愚蠢的好的实现方式进行比较:

Compare with this contrived and silly good implementation:

#include<iostream>
#include<stdexcept>

struct Resource {

  Resource() {
    std::cout<<"Resource acquired"<<std::endl;    
  }

  ~Resource() {
    std::cout<<"Resource cleaned up"<<std::endl;        
  }

};

struct Good {
  Good() {
    std::cout<<"Acquiring resource"<<std::endl;
    Resource r;
    throw(std::runtime_error("the exception was thrown"));
  }

  ~Good() {
    std::cout<<"My destructor was called"<<std::endl;
  }  
};


int main() {
  try {
    Good good;
  } catch (const std::exception &e) {
    std::cout<<"We DO NOT have a leak! Let's keep going!"<<std::endl;
  }
  std::cout<<"Here I am... without a leak..."<<std::endl;
  return 0;
}

输出:

Acquiring resource
Resource acquired
Resource cleaned up
We DO NOT have a leak! Let's keep going!
Here I am... without a leak...

我的意思是:尝试将所有需要释放的资源封装到自己的类中,在该类中构造函数不会抛出该异常,而析构函数会正确释放该资源.然后,在析构函数可能抛出的其他类上,只需创建包装的资源的实例,就可以保证清除所获取的资源包装程序的析构函数.

My point is the following: try to encapsulate all resources which need to be liberated into their own class where the constructor does NOT throw, and the destructor correctly releases the resource. Then, on the other classes where the destructor may throw, simply create instances of the wrapped resource and the destructors of the acquired resource wrappers will be guaranteed to clean-up.

以下可能是一个更好的示例:

The following is probably a better example:

#include<mutex>
#include<iostream>
#include<stdexcept>

// a program-wide mutex
std::mutex TheMutex;

struct Bad {
  Bad() {
    std::cout<<"Attempting to get the mutex"<<std::endl;
    TheMutex.lock();
    std::cout<<"Got it! I'll give it to you in a second..."<<std::endl;
    throw(std::runtime_error("Ooops, I threw!"));
    // will never get here...
    TheMutex.unlock();
    std::cout<<"There you go! I released the mutex!"<<std::endl;    
  }  
};

struct ScopedLock {
  ScopedLock(std::mutex& mutex)
      :m_mutex(&mutex) {
    std::cout<<"Attempting to get the mutex"<<std::endl;
    m_mutex->lock();
    std::cout<<"Got it! I'll give it to you in a second..."<<std::endl;    
  }

  ~ScopedLock() {
    m_mutex->unlock();
    std::cout<<"There you go! I released the mutex!"<<std::endl;        
  }
  std::mutex* m_mutex;      
};

struct Good {
  Good() {
    ScopedLock autorelease(TheMutex);
    throw(std::runtime_error("Ooops, I threw!"));
    // will never get here
  }  
};


int main() {
  std::cout<<"Create a Good instance"<<std::endl;
  try {
    Good g;
  } catch (const std::exception& e) {
    std::cout<<e.what()<<std::endl;
  }

  std::cout<<"Now, let's create a Bad instance"<<std::endl;
  try {
    Bad b;
  } catch (const std::exception& e) {
    std::cout<<e.what()<<std::endl;
  }

  std::cout<<"Now, let's create a whatever instance"<<std::endl;
  try {
    Good g;
  } catch (const std::exception& e) {
    std::cout<<e.what()<<std::endl;
  }

  std::cout<<"I am here despite the deadlock..."<<std::endl;  
  return 0;
}

输出(使用-std=c++11gcc 4.8.1编译):

Create a Good instance
Attempting to get the mutex
Got it! I'll give it to you in a second...
There you go! I released the mutex!
Ooops, I threw!
Now, let's create a Bad instance
Attempting to get the mutex
Got it! I'll give it to you in a second...
Ooops, I threw!
Now, let's create a whatever instance
Attempting to get the mutex

现在,请不要按照我的示例创建自己的示波器. C ++(特别是C ++ 11)在设计时考虑了RAII,并提供了大量的生命周期管理器.例如,一个std::fstream将自动关闭,一个[std::lock_guard][2]将执行我在示例中尝试执行的操作,并且std::unique_ptrstd::shared_ptr将负责销毁.

Now, please don't follow my example and create your own scope guard. C++ (specially C++11) are designed with RAII in mind and provide a wealth of lifetime managers. For example, an std::fstream will automatically close, an [std::lock_guard][2] will do what I attempted to do in my example, and either std::unique_ptr or std::shared_ptr will take care of destruction.

最好的建议?阅读有关RAII的知识(并根据它进行设计),使用标准库,不要创建裸露的资源,并熟悉Herb Sutter关于异常安全性"必须说的话(继续阅读他的网站或Google"

The best advice? Read about RAII (and design according to it), use the Standard Library, don't create naked resources, and get familiarized with what Herb Sutter has to say with respect to "exception safety" (go ahead and read his website, or google "Herb Sutter Exception Safety")

这篇关于防止在构造函数中引发异常时防止内存泄漏的最佳实践是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆