美文网首页
C++ 中构造函数抛出异常处理的惯用手法

C++ 中构造函数抛出异常处理的惯用手法

作者: algebra2k | 来源:发表于2020-03-29 19:55 被阅读0次

    C++异常属于C++语法比较难得一个子集,并且关于C++是否使用异常有很多争论,这里暂且不谈。
    由于构造函数在fail的时候无法返回error code, 因此无法向C一样使用error code。常用的手法是两阶段构造,但感觉违背了RAII的哲学。

    https://isocpp.org/ 一个FAQ中指出了关于C++构造函数出现错误的处理方式,具体可以看这个链接 How can I handle a constructor that fails?

    原文如下:

    Throw an exception.
    Constructors don’t have a return type, so it’s not possible to use return codes. The best way to signal constructor failure is therefore to throw an exception. If you don’t have the option of using exceptions, the “least bad” work-around is to put the object into a “zombie” state by setting an internal status bit so the object acts sort of like it’s dead even though it is technically still alive.
    The idea of a “zombie” object has a lot of down-side. You need to add a query (“inspector”) member function to check this “zombie” bit so users of your class can find out if their object is truly alive, or if it’s a zombie (i.e., a “living dead” object), and just about every place you construct one of your objects (including within a larger object or an array of objects) you need to check that status flag via an if statement. You’ll also want to add an if to your other member functions: if the object is a zombie, do a no-op or perhaps something more obnoxious.
    In practice the “zombie” thing gets pretty ugly. Certainly you should prefer exceptions over zombie objects, but if you do not have the option of using exceptions, zombie objects might be the “least bad” alternative.
    Note: if a constructor finishes by throwing an exception, the memory associated with the object itself is cleaned up — there is no memory leak. For example:

    void f()
    {
      X x;             // If X::X() throws, the memory for x itself will not leak
      Y* p = new Y();  // If Y::Y() throws, the memory for *p itself will not leak
    }
    

    There is some fine print on this topic, so you need to keep reading. Specifically you need to know how to prevent memory leaks if the constructor itself allocates memory, and you also need to be aware of what happens if you use “placement” new rather than the ordinary new used in the sample code above.

    概括一下就是如果忽略构造函数产生的错误,则需要检查该构造函数产生的对象是否是 zombie 的,因此代码中会充斥着大量的if。
    如果采用exception的方式,则编译器会terminate,并知道“回收已经分配的内存”。
    这里的回收是有一些歧义的,应该是只回收编译器分配的用于构造对象的地址空间,而你对象内部编译器是看不到的,因此会造成 memory leak,举个例子

    // 下面代码会产生memory leak
    #include <iostream>
    #include <exception>
    #include <cstring>
    
    class FooExcetion: public std::exception
    {
    public:
        virtual char const* what() const noexcept { 
            return "foo exception";
         };
    };
    
    class Foo
    {
    public:
        Foo() {
            __foo = new char[4];
            memset(__foo, '\0', 4);
            strcpy(__foo, "foo");
            std::cout << "call Foo() and alloc some memory" << std::endl;
            std::cout << __foo << std::endl;
            throw FooExcetion();
        };
    
        ~Foo() {
           std::cout << "call ~Foo()" << std::endl; 
            delete []__foo;
        };
    
    private:
        char* __foo;
    };
    
    
    int main() 
    {
        try {
            Foo f;
        } catch (FooExcetion& e) {
            std::cout << e.what() << std::endl;
        }
    }
    

    处理这个问题的手法是 rethrow

    #include <iostream>
    #include <exception>
    #include <cstring>
    
    class FooExcetion: public std::exception
    {
    public:
        virtual char const* what() const noexcept { 
            return "foo exception";
         };
    };
    
    class Foo
    {
    public:
        Foo() {
            try {
                 __foo = new char[4];
                memset(__foo, '\0', 4);
                strcpy(__foo, "foo");
                std::cout << "call Foo() and alloc some memory" << std::endl;
                throw FooExcetion();
            } catch (FooExcetion& e) {
                delete []__foo;
                std::cout << "call delete []__foo and free some memory" << std::endl;
                throw e; // 重新抛出,并且没有memory leak
            }
        };
    
        ~Foo() {
           std::cout << "call ~Foo()" << std::endl; 
            delete []__foo;
        };
    
    private:
        char* __foo;
    };
    
    
    int main() 
    {
        try {
            Foo f;
        } catch (FooExcetion& e) {
            std::cout << e.what() << std::endl;
        }
    }
    

    相关文章

      网友评论

          本文标题:C++ 中构造函数抛出异常处理的惯用手法

          本文链接:https://www.haomeiwen.com/subject/fonuuhtx.html