我创建了一个类,如果一个成员为空,我不想创建该对象。这些是代码行:
#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;
}
}
答案 0 :(得分:7)
希望m_image
和m_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:
编辑:澄清了我的懒惰