简体   繁体   English

C ++构造函数中的异常消除

[英]Exception elimination in C++ constructors

We have recently been faced with the problem of porting our C++ framework to an ARM platform running uClinux where the only vendor supported compiler is GCC 2.95.3. 我们最近遇到了将C ++框架移植到运行uClinux的ARM平台的问题,其中唯一受供应商支持的编译器是GCC 2.95.3。 The problem we have run into is that exceptions are extremely unreliable causing everything from not being caught at all to being caught by an unrelated thread(!). 我们遇到的问题是,异常非常不可靠,导致一切从未被捕获到被不相关的线程(!)捕获。 This seems to be a documented bug, ie here and here . 这似乎是一个记录在案的错误,即此处此处

After some deliberation we decided to eliminate exceptions altoghether as we have reached a point where exceptions do a lot of damage to running applications. 经过一番考虑后,我们决定消除异常,因为我们已经达到了异常会对正在运行的应用程序造成大量破坏的程度。 The main concern now is how to manage cases where a constructor failed. 现在主要关注的是如何管理构造函数失败的情况。

We have tried lazy evaluation , where each method has the ability to instantiate dynamic resources and return a status value but that means that every class method has to return a return value which makes for a lot of ifs in the code and is very annoying in methods which generally would never cause an error. 我们尝试了懒惰的评估 ,其中每个方法都能够实例化动态资源并返回状态值,但这意味着每个类方法都必须返回一个返回值,这会在代码中产生很多 ifs并且在方法中非常烦人这通常不会导致错误。

We looked into adding a static create method which returns a pointer to a created object or NULL if creation failed but that means we cannot store objects on the stack anymore, and there is still need to pass in a reference to a status value if you want to act on the actual error. 我们研究了添加静态create方法,该方法返回指向创建对象的指针,如果创建失败则返回NULL,但这意味着我们不能再将对象存储在堆栈中,如果需要,仍需要传入对状态值的引用对实际错误采取行动。

According to Google's C++ Style Guide they do not use exceptions and only do trivial work in their constructors, using an init method for non-trivial work ( Doing Work in Constructors ). 根据谷歌的C ++风格指南,他们不使用异常 ,只在他们的构造函数中做一些简单的工作,使用init方法进行非平凡的工作( 在构造函数中做工作 )。 I cannot however find anything about how they handle construction errors when using this approach. 但是,在使用这种方法时,我无法找到关于它们如何处理构造错误的任何信息

Has anyone here tried eliminating exceptions and come up with a good solution to handling construction failure? 有没有人在这里试图消除异常并提出一个很好的解决方案来处理施工失败?

Generally you end up with code like this for objects on the stack: 通常,对于堆栈中的对象,最终会得到这样的代码:

MyClassWithNoThrowConstructor foo;
if (foo.init(bar, baz, etc) != 0) {
    // error-handling code
} else {
    // phew, we got away with it. Now for the next object...
}

And this for objects on the heap. 这适用于堆上的对象。 I assume you override global operator new with something that returns NULL instead of throwing, to save yourself remembering to use nothrow new everywhere: 我假设您使用返回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
}

Obviously if you possibly can, use smart pointers to save having to remember the deletes, but if your compiler doesn't support exceptions properly, then you might have trouble getting Boost or TR1. 显然,如果你可以,使用智能指针来节省必须记住删除,但如果你的编译器不能正确支持异常,那么你可能无法获得Boost或TR1。 I don't know. 我不知道。

You also might want to structure the logic differently, or abstract the combined new and init, to avoid deeply-nested "arrow code" whenever you're handling multiple objects, and to common-up the error-handling between the two failure cases. 您还可能希望以不同方式构造逻辑,或者抽象组合的new和init,以避免在处理多个对象时深层嵌套的“箭头代码”,并在两个故障情况之间共同处理错误。 The above is just the basic logic in its most painstaking form. 以上只是最辛苦形式的基本逻辑。

In both cases, the constructor sets everything to default values (it can take some arguments, provided that what it does with those arguments cannot possibly fail, for instance if it just stores them). 在这两种情况下,构造函数都将所有内容设置为默认值(它可以采用一些参数,前提是它对这些参数的作用不可能失败,例如,如果它只存储它们)。 The init method can then do the real work, which might fail, and in this case returns 0 success or any other value for failure. 然后,init方法可以执行可能失败的实际工作,并且在这种情况下返回0成功或任何其他失败值。

You probably need to enforce that every init method across your whole codebase reports errors in the same way: you do not want some returning 0 success or a negative error code, some returning 0 success or a positive error code, some returning bool, some returning an object by value that has fields explaining the fault, some setting global errno, etc. 您可能需要强制执行整个代码库中的每个init方法以相同的方式报告错误:您希望某些返回0成功或负错误代码,一些返回0成功或一个肯定的错误代码,一些返回bool,一些返回按值的对象,具有解释故障的字段,一些设置全局错误等。

You could perhaps take a quick look at some Symbian class API docs online. 您也许可以在线快速浏览一些Symbian类API文档。 Symbian uses C++ without exceptions: it does have a mechanism called "Leave" that partially makes up for that, but it's not valid to Leave from a constructor, so you have the same basic issue in terms of designing non-failing constructors and deferring failing operations to init routines. Symbian使用C ++而没有例外:它确实有一个名为“Leave”的机制,它部分地弥补了这一点,但是从构造函数中保留是无效的,所以在设计非失败构造函数和推迟失败方面你有相同的基本问题对init例程的操作。 Of course with Symbian the init routine is permitted to Leave, so the caller doesn't need the error-handling code I indicate above, but in terms of splitting work between a C++ constructor and an additional init call, it's the same. 当然,使用Symbian,init例程被允许保留,因此调用者不需要我在上面指出的错误处理代码,但是就C ++构造函数和其他init调用之间的拆分工作而言,它是相同的。

General principles include: 一般原则包括:

  • If your constructor wants to get a value from somewhere in a way that might fail, defer that to the init and leave the value default-initialised in the ctor. 如果构造函数想要以某种可能失败的方式从某个地方获取值,请将其推迟到init并将值默认初始化为ctor。
  • If your object holds a pointer, set it to null in the ctor and set it "properly" in the init. 如果您的对象持有指针,请在ctor中将其设置为null并在init中“正确”设置它。
  • If your object holds a reference, either change it to a (smart) pointer so that it can null to start with, or else make the caller pass the value into the constructor as a parameter instead of generating it in the ctor. 如果您的对象包含引用,则将其更改为(智能)指针,以便它可以为null开始,或者使调用者将值作为参数传递给构造函数,而不是在ctor中生成它。
  • If your constructor has members of object type, then you're fine. 如果您的构造函数具有对象类型的成员,那么您没问题。 Their ctors won't throw either, so it's perfectly OK to construct your members (and base classes) in the initializer list in the usual way. 他们的ctors也不会抛出,所以以通常的方式在初始化列表中构造你的成员(和基类)是完全可以的。
  • Make sure you keep track of what's set and what isn't, so that the destructor works when the init fails. 确保跟踪设置的内容和不设置的内容,以便析构函数在init失败时工作。
  • All functions other than constructors, the destructor, and init, can assume that init has succeeded, provided you document for your class that it is not valid to call any method other than init until init has been called and succeeded. 除了构造函数,析构函数和init之外的所有函数都可以假定init已成功,前提是您的类文档在调用init并成功之前调用除init之外的任何方法无效。
  • You can offer multiple init functions, which unlike constructors can call each other, in the same way that for some classes you'd offer multiple constructors. 您可以提供多个init函数,这些函数与构造函数可以相互调用,就像您为某些类提供多个构造函数一样。
  • You can't provide implicit conversions that might fail, so if your code currently relies on implicit conversions which throw exceptions then you have to redesign. 您无法提供可能失败的隐式转换,因此如果您的代码当前依赖于抛出异常的隐式转换,那么您必须重新设计。 Same goes for most operator overloads, since their return types are constrained. 大多数操作符重载也是如此,因为它们的返回类型受到约束。

You could use a flag to keep track of whether the constructor failed. 您可以使用标志来跟踪构造函数是否失败。 You might already have a member variable that's only valid if the constructor succeeds, eg 您可能已经有一个成员变量,只有在构造函数成功时才有效,例如

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
}

Regarding the Google reference (you couldn't find how they handled errors in the constructor): 关于Google引用(您无法找到它们如何处理构造函数中的错误):

The answer to that part is that if they only do trivial work in the constructor, then there are no errors. 这部分的答案是,如果他们只在构造函数中做了琐碎的工作,那么就没有错误。 Because the work is trivial, they are pretty confident (backed up by thorough testing, I'm sure) that exceptions just won't be thrown. 因为工作是微不足道的,所以他们非常有信心(通过彻底的测试支持,我敢肯定)不会抛出异常。

I guess to a large extent, it depends what type of exceptions are typically occurring. 我想在很大程度上,它取决于通常发生的异常类型。 My assumption is they are primarily resource related. 我的假设是他们主要与资源有关。 If this is the case, a solution that I have used previously on an embedded C system was to allocate / commit all potentially required resources at the start of the program. 如果是这种情况,我之前在嵌入式C系统上使用的解决方案是在程序开始时分配/提交所有可能需要的资源。 Thus I knew that all required resources were available at execution time rather than during the run. 因此,我知道所有必需的资源在执行时而不是在运行期间可用。 It's a greedy solution that may interfere with interoperability with other software, but it worked quite well for me. 这是一个贪婪的解决方案,可能会干扰与其他软件的互操作性,但它对我来说非常有效。

If you really can't use exceptions, you can also write a construction macro doing what onebyone proposed always. 如果你真的不能使用异常,你也可以编写一个构造宏来做一个人总是提出的建议。 So you don't get into the hassle of doing this creation/init/if cycle all the time and most important, you never forget to initialize an object. 因此,您不会遇到执行此创建的麻烦/ init /如果一直循环,最重要的是,您永远不会忘记初始化对象。

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))

The error_type struct will invert the condition, so that errors are checked in the else part of the if. error_type结构将反转条件,以便在if的else部分中检查错误。 Now write an init function that returns 0 on success, or any other value indicating the error code. 现在编写一个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
}

You can add other functions to error_type , for example stuff that looks up what the code means. 您可以向error_type添加其他函数,例如查找代码含义的内容。

If a constructor is only doing trivial things like initializing POD variables (and calling other trivial constructors implicitly), then it can't possibly fail. 如果构造函数只是在做一些微不足道的事情,比如初始化POD变量(并隐式调用其他普通的构造函数),那么它就不会失败。 See the C++ FQA ; 参见C ++ FQA ; see also why you shouldn't use C++ exceptions . 另请参阅为什么不应该使用C ++异常

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM