在构造函数c ++中抛出异常

时间:2014-07-18 12:01:15

标签: c++

我创建了一个类,如果一个成员为空,我不想创建该对象。这些是代码行:

#include "verification/CVerifObj.hpp"

VerifObj::VerifObj(const fs::path& imgNameIn)
{
    m_image = cv::imread(imgNameIn.string());
    AnalyseCSV csv;
    m_plateContour = csv.getPlateRegion(imgNameIn); // search for the csv file and load the values
    if (m_plateContour.empty()) // throw exception if empty csv
    {
        throw EmptyContourException(imgNameIn.string());
    }
    m_imageName = imgNameIn.string();
}

VerifObj::~VerifObj()
{
    // are these enough for destructor?
    m_image.release();
    m_plateContour.clear();
}

这样可以,还是我会做更多的事情?如果抛出异常,我怎么能确定没有创建对象?

我有以下几行代码来确保它:

for(fs::directory_iterator iter(folderIn); iter != end; ++iter)
{
    if(!fs::is_regular_file(iter->status()))
    {
        continue;
    }
    std::string extension = iter->path().extension().string();
    if(targetExtensions.find(extension) == targetExtensions.end()) 
    { 
        continue;
    }
    try
    {
        VerifObj verifObj(iter->path());
        verifObjVecRet.push_back(verifObj);
    }
    catch (EmptyContourException& ece)
    {
        continue;
    }
    catch (CSVException &csve)
    {
        continue;
    }
}

2 个答案:

答案 0 :(得分:7)

希望m_imagem_plateContour(以及您班级中的任何其他非平凡成员)都是经过适当设计的RAII类型,使用析构函数清理他们可能拥有的任何资源。

在这种情况下,你的班级根本不需要一个析构函数,如果你的构造函数抛出,所有成员都会被自动正确销毁 - 那里不需要采取任何行动。

但是,析构函数的存在意味着它们可能是邪恶类型,需要在破坏之前手动清理。在这种情况下,修复它们。

如果由于某种原因你无法解决问题,那么在投掷之前你需要m_image.release();。您还需要大量的咖啡供应,因为这样的类会导致长时间的调试会话,试图修复内存泄漏。

答案 1 :(得分:0)

简短的回答是,在构造函数中抛出东西是危险的。

首先,让我们定义设计问题:你有一个无法初始化的类。如果类无法初始化,则无法使用(如果使用则会导致其他错误),因此类失败被认为是“严重失败”,至少是关于类的地方。

简而言之,我们要避免的是让用户使用无法初始化的类。

想象一下以下情况:

class A
{
  public:
  A()
  {
    stuff1 = malloc(100);//some dynamic memory allocation
    throw "This throw is crazy";
    stuff2 = malloc(100);//some dynamic memory allocation
  }
  ~A() {free(stuff1); free(stuff2);}
  private: void* stuff2;void* stuff2;
};

int main(int argc, char** argv)
{
  A a;
}

一旦你投入构造函数,会发生什么?好吧,它是瞬间内存泄漏。析构函数永远不会被调用。 baaaad是哪个。如果您处理异常:

int main(int argc, char** argv)
{
  try
  {
  A a;
  }
  catch(...)
  {
     //can't do much here
  }
}

你已经失去了对A的引用,这是一场噩梦。所以有些人试图躲开这个(前面,它仍然很糟糕)

int main(int argc, char** argv)
{
   A* a;

   try { a= new A();}
   catch(...){delete a;}
}

但这同样糟糕。你可能仍然有一个引用(和一个不会泄漏的直接指向的内存),但是a现在处于未知状态......你仍然需要在某一天删除它,而免费的stuff2失败了。

解决这个问题的一种方法是让你的构造函数和析构函数变得更聪明。捕获可以在构造函数中抛出的任何异常,并清理对象,返回“僵尸”对象。并且让析构函数能够轻松地检查僵尸对象。我发现这种方法更复杂。

更好的方法是使用初始化程序:

 class A
 {
 public:
    A() {stuff1=null; stuff2=null;}
    void init()
    {
      stuff1 = malloc(100);//some dynamic memory allocation
      throw "This throw is crazy";
      stuff2 = malloc(100);//some dynamic memory allocation
    }
    void destroy() {if (stuff1) {delete stuff1; stuff1=NULL;} if (stuff2) {delete stuff2; stuff2=NULL;}}
    ~A() {destroy();}
 };

我不喜欢这个,因为你仍然得到“僵尸”对象,并且拥有调用init和destroy的所有额外维护。回到最初的问题,更聪明的构造函数和提供初始化器仍然无法解决简单的声明:最终用户仍然(很容易,实际上)使用处于未知状态的实例。

我喜欢在这里遵循伪RAII(资源获取是初始化):你想要确保如果你有一个对象的引用,它是一个有效的引用。否则,应该没有内存泄漏。实现这一目标的最佳方法(恕我直言)是使用工厂方法,并将所有初始化保持为私有。

 class A
 {
 public:
    ~A() {destroy();}
     static A* MakeA()
     {
         A* ret = new A();
         try { ret->init();}
         catch(...)
         {
            delete ret;
            return NULL;
         }
         return ret;
     }
  private: void* stuff1,*stuff2;
          A() {stuff1=null; stuff2=null;}
    void init()
    {
      stuff1 = malloc(100);//some dynamic memory allocation
      throw "This throw is crazy";
      stuff2 = malloc(100);//some dynamic memory allocation
    }
    void destroy() {if (stuff1) {delete stuff1; stuff1=NULL;} if (stuff2) {delete stuff2; stuff2=NULL;}}
 };

现在,用户永远无法对失败的对象进行有效的引用,这很不错。

Protips:

  1. 异常处理很慢。如果您希望经常发生这种情况,或者认为这不会是致命错误,请在init语句中使用错误代码。工厂方法中的错误代码也可以为用户提供更多信息。
  2. 我在这个答案中使用了原始指针因为我懒得在答案中写下所有这些字符。始终使用智能指针,这变得更加容易。杂耍原始指针只是为了犯罪疯狂。 (澄清使用智能指针)
  3. 编辑:澄清了我的懒惰