我们最近遇到了将C ++框架移植到运行uClinux的ARM平台的问题,其中唯一受供应商支持的编译器是GCC 2.95.3。我们遇到的问题是,异常非常不可靠,导致一切从未被捕获到被不相关的线程(!)捕获。这似乎是一个记录在案的错误,即here和here。
经过一番考虑,我们决定消除异常,因为我们已经达到了异常会对运行的应用程序造成大量破坏的程度。现在主要关注的是如何管理构造函数失败的情况。
我们已经尝试了lazy evaluation,其中每个方法都能够实例化动态资源并返回状态值,但这意味着每个类方法都必须返回一个返回值,该值返回批次代码中的if>,并且在通常永远不会导致错误的方法中非常烦人。
我们考虑添加静态 create 方法,该方法返回指向创建对象的指针,如果创建失败则返回NULL,但这意味着我们不能再将对象存储在堆栈中,并且仍然需要通过如果您想对实际错误采取行动,请参考状态值。
根据谷歌的C ++风格指南,他们do not use exceptions只在他们的构造函数中做了一些微不足道的工作,使用init方法进行非平凡的工作(Doing Work in Constructors)。但是,在使用这种方法时,我无法找到关于它们如何处理构造错误的任何信息。
有没有人在这里试图消除异常并提出一个很好的解决方案来处理施工失败?
答案 0 :(得分:13)
通常,对于堆栈中的对象,最终会得到这样的代码:
MyClassWithNoThrowConstructor foo;
if (foo.init(bar, baz, etc) != 0) {
// error-handling code
} else {
// phew, we got away with it. Now for the next object...
}
这适用于堆上的对象。我假设您使用返回NULL而不是抛出的东西覆盖全局operator new,以保存自己记住在任何地方使用nothrow:
MyClassWithNoThrowConstructor *foo = new MyClassWithNoThrowConstructor();
if (foo == NULL) {
// out of memory handling code
} else if (foo->init(bar, baz, etc) != 0) {
delete foo;
// error-handling code
} else {
// success, we can use foo
}
显然,如果你可以,使用智能指针来节省必须记住删除,但如果你的编译器不能正确支持异常,那么你可能无法获得Boost或TR1。我不知道。
您也可能希望以不同的方式构造逻辑,或者抽象组合的new和init,以避免在处理多个对象时深层嵌套的“箭头代码”,并且共同处理两者之间的错误处理失败的情况。以上只是最辛苦形式的基本逻辑。
在这两种情况下,构造函数都将所有内容设置为默认值(它可以采用一些参数,前提是它对这些参数的作用不可能失败,例如,如果它只存储它们)。然后,init方法可以执行可能失败的实际工作,并且在这种情况下返回0成功或任何其他失败值。
您可能需要强制执行整个代码库中的每个init方法以相同的方式报告错误:不希望某些返回0成功或否定错误代码,某些返回0成功或者正面错误代码,一些返回bool,一些按值返回一个对象,其中包含解释错误的字段,一些设置全局错误等等。
您也许可以在线快速浏览一些Symbian类API文档。 Symbian使用C ++而没有例外:它确实有一个名为“Leave”的机制,它部分地弥补了这一点,但是从构造函数中保留是无效的,所以在设计非失败构造函数和推迟失败方面你有相同的基本问题对init例程的操作。当然,使用Symbian,init例程被允许保留,因此调用者不需要我在上面指出的错误处理代码,但是在C ++构造函数和其他init调用之间拆分工作方面,它是相同的。
一般原则包括:
答案 1 :(得分:1)
您可以使用标志来跟踪构造函数是否失败。您可能已经有一个成员变量,只有在构造函数成功时才有效,例如
class MyClass
{
public:
MyClass() : m_resource(NULL)
{
m_resource = GetResource();
}
bool IsValid() const
{
return m_resource != NULL;
}
private:
Resource * m_resource;
};
MyClass myobj;
if (!myobj.IsValid())
{
// error handling goes here
}
答案 2 :(得分:0)
关于Google引用(您无法找到它们如何处理构造函数中的错误):
该部分的答案是,如果他们只在构造函数中做了琐碎的工作,那么就没有错误。因为工作是微不足道的,所以他们非常有信心(通过彻底的测试支持,我敢肯定)不会抛出异常。
答案 3 :(得分:0)
我认为在很大程度上,它取决于通常发生的异常类型。我的假设是他们主要与资源有关。如果是这种情况,我之前在嵌入式C系统上使用的解决方案是在程序开始时分配/提交所有可能需要的资源。因此,我知道所有必需的资源在执行时而不是在运行期间可用。这是一个贪婪的解决方案,可能会干扰与其他软件的互操作性,但它对我来说效果很好。
答案 4 :(得分:0)
如果你真的不能使用异常,你也可以编写一个构造宏来做一个人总是提出的建议。因此,您不必为执行此操作而烦恼 creation / init / if 一直循环,最重要的是,您永远不会忘记初始化对象。
struct error_type {
explicit error_type(int code):code(code) { }
operator bool() const {
return code == 0;
}
int get_code() { return code; }
int const code;
};
#define checked_construction(T, N, A) \
T N; \
if(error_type const& error = error_type(N.init A))
error_type结构将反转条件,以便在if的else
部分中检查错误。现在编写一个init函数,在成功时返回0
,或者指示错误代码的任何其他值。
struct i_can_fail {
i_can_fail() {
// constructor cannot fail
}
int init(std::string p1, bool p2) {
// init using the given parameters
return 0; // successful
}
};
void do_something() {
checked_construction(i_can_fail, name, ("hello", true)) {
// alright. use it
name.do_other_thing();
} else {
// handle failure
std::cerr << "failure. error: " << error.get_code() << std::endl;
}
// name is still in scope. here is the common code
}
您可以向error_type
添加其他功能,例如查找代码含义的内容。
答案 5 :(得分:-4)
如果构造函数只执行初始化POD变量(以及隐式调用其他普通构造函数)等微不足道的事情,那么它就不会失败。见C++ FQA;另见why you shouldn't use C++ exceptions。