C+中的异常消除+;建设者 我们最近面临的问题是把我们的C++框架移植到运行uClinux的ARM平台上,其中只有供应商支持的编译器是GCC 2.95.3。我们遇到的问题是异常非常不可靠,导致从根本没有被捕获到被无关线程捕获(!)。这似乎是一个记录在案的bug,即和
经过深思熟虑,我们决定消除异常,因为我们已经到了异常对正在运行的应用程序造成很大损害的地步。现在主要关心的是如何管理构造函数失败的情况 我们尝试过,每个方法都能够实例化动态资源并返回一个状态值,但这意味着每个类方法都必须返回一个返回值,这会在代码中产生大量的ifs,并且在通常不会导致错误的方法中非常烦人 我们研究了添加一个静态create方法,该方法在创建失败时返回一个指向已创建对象的指针或NULL,但这意味着我们不能再在堆栈上存储对象了,如果要处理实际错误,仍然需要传入对状态值的引用C+中的异常消除+;建设者 我们最近面临的问题是把我们的C++框架移植到运行uClinux的ARM平台上,其中只有供应商支持的编译器是GCC 2.95.3。我们遇到的问题是异常非常不可靠,导致从根本没有被捕获到被无关线程捕获(!)。这似乎是一个记录在案的bug,即和,c++,exception,C++,Exception,经过深思熟虑,我们决定消除异常,因为我们已经到了异常对正在运行的应用程序造成很大损害的地步。现在主要关心的是如何管理构造函数失败的情况 我们尝试过,每个方法都能够实例化动态资源并返回一个状态值,但这意味着每个类方法都必须返回一个返回值,这会在代码中产生大量的ifs,并且在通常不会导致错误的方法中非常烦人 我们研究了添加一个静态create方法,该方法在创建失败时返回一个指向已创建对象的指针或NULL,但这意味着我们不能再在堆栈上存储对象了,如果要处理实际错误,仍然需要传入对状态值的引用 根据谷歌
根据谷歌的C++风格指南,只在构造函数中做琐碎的工作,使用init方法进行非平凡工作()。然而,我找不到任何关于他们在使用这种方法时如何处理构造错误的信息
这里有没有人尝试过消除异常并提出处理构造失败的好解决方案?关于谷歌参考(你找不到他们是如何处理构造函数中的错误的):
这一部分的答案是,如果他们只在构造函数中做一些琐碎的工作,那么就不会有错误。因为这项工作很琐碎,所以他们非常有信心(我确信有彻底的测试支持)不会抛出异常。我想这在很大程度上取决于通常发生的异常类型。我的假设是,它们主要与资源相关。如果是这种情况,我以前在嵌入式C系统上使用的解决方案是在程序开始时分配/提交所有可能需要的资源。因此,我知道所有必需的资源都在执行时可用,而不是在运行期间。这是一个贪婪的解决方案,可能会干扰与其他软件的互操作性,但对我来说效果很好。如果一个构造函数只做一些琐碎的事情,比如初始化POD变量(并隐式调用其他琐碎的构造函数),那么它就不可能失败。看,;另请参见。通常,对于堆栈上的对象,您会得到如下代码:
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而不是抛出的内容覆盖全局运算符new,以避免记住在任何地方都使用nothrow new:
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 success或任何其他失败值
您可能需要强制整个代码库中的每个init方法以相同的方式报告错误:您不希望一些方法返回0成功或负错误代码,一些方法返回0成功或正错误代码,一些方法返回bool,一些方法返回具有解释错误字段的值的对象,一些设置全局错误号等
您也许可以在线快速查看一些Symbian类API文档。Symbian使用C++没有例外:它确实有一种称为“Lead”的机制,它部分弥补了这个缺点,但是从构造函数中离开是无效的,因此在设计非失败构造函数和推迟失败操作到init例程方面,你有相同的基本问题。当然,使用Symbian,允许init例程离开,所以调用方不需要上面所指出的错误处理代码,但是就C++构造函数和附加init调用之间的拆分工作而言,它是相同的。
一般原则包括:
- 如果您的构造函数希望以可能失败的方式从某处获取值,请将其推迟到init,并在ctor中保留默认值初始化
- 如果您的对象持有指针,请在ctor中将其设置为null,并在init中将其设置为“正确”
- 如果您的对象持有引用,请将其更改为(智能)指针,以便它可以以null开头,或者让调用方将值作为参数传递到构造函数中,而不是在构造函数中生成它
- 如果您的构造函数有对象类型的成员,那么您就可以了。它们的CTOR也不会抛出,所以用通常的方式在初始值设定项列表中构造成员(和基类)是完全可以的
- 确保跟踪设置的内容和未设置的内容,以便在init失败时析构函数工作
- 除了构造函数、析构函数和init之外的所有函数都可以假定init已成功,前提是您为类编写文档,说明在调用init并成功之前,调用init以外的任何方法都是无效的
- 您可以提供多个init函数,这与c不同
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))
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 }
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 }