如何在抛出异常时阻止构造函数创建对象

kmi*_*las 7 c++ constructor exception-handling try-catch c++11

当构造函数抛出异常时,如何防止创建对象?

在下面的示例中,我创建了一个Month()类,其int month_属性的合法值在1到12的范围内.I实例化12月,或者 dec,使用整数值13.抛出异常,因为它应该是,但仍然创建了该对象.然后调用析构函数.

如何在抛出异常时中止创建类实例?

OUTPUT

-- Month() constructor called for value: 2
-- Month() constructor called for value: 6
-- Month() constructor called for value: 13
EXCEPTION: Month out of range
2
6
13
-- ~Month() destructor called.
-- ~Month() destructor called.
-- ~Month() destructor called.
Press any key to exit
Run Code Online (Sandbox Code Playgroud)

最小,完整和可验证的例子

#include <iostream>
#include <string>

class Month {

    public:
        Month(int month) {
            std::cout << "-- Month() constructor called for value: " << month << std::endl;
            try {
                // if ((month < 0) || month > 12) throw 100; Good eye, Nat!
                if ((month < 1) || month > 12) throw 100;
            } catch(int e) {
                if (e == 100) std::cout << "EXCEPTION: Month out of range" << std::endl;
            }
            month_ = month;
        }

        ~Month() {
            std::cout << "-- ~Month() destructor called." << std::endl;
        }

        int getMonth()const { return month_; }

    private:
        int month_;
};

int makeMonths() {
    Month feb(2), jun(6), dec(13);
    std::cout << feb.getMonth() << std::endl;
    std::cout << jun.getMonth() << std::endl;
    std::cout << dec.getMonth() << std::endl;
    return 0;
}

int main() {
    makeMonths();
    std::cout << "Press any key to exit"; std::cin.get();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Rak*_*111 15

如何在抛出异常时中止创建类实例?

好吧,你在构造函数中抛出一个异常.但有一个问题:不要抓住它!

如果你抓住它,就会发生异常从未发生过.你抓住了它,所以它不再上调调用堆栈了.所以创建了对象,因为就任何人而言,构造函数都没有抛出异常.

如果你catch从构造函数中删除子句,你可能会得到类似的东西:

-- Month() constructor called for value: 2
-- Month() constructor called for value: 6
-- Month() constructor called for value: 13
terminate called after throwing an instance of 'int'
[1]    28844 abort (core dumped)  ./main
Run Code Online (Sandbox Code Playgroud)

这里,构造函数引发了一个异常,这次它在构造函数之外的调用堆栈上升,因为没有人在构造函数中捕获它.然后它会进入makeMonths,它也没有被捕获,然后到达main,它也没有被捕获,因此程序异常终止.


Sof*_*re2 10

默认情况下,在构造函数中抛出异常应该可以防止析构函数被调用.但是,您正在捕获异常并处理它.

您可以在catch中抛出一个新异常,然后可以在此范围之外看到,以便不调用析构函数.


小智 5

如何在抛出异常时中止类实例的创建?

你只是(重新)抛出异常,而不是捕捉它,我建议抛出一个std::invalid_argumentstd::out_of_range异常:

class Month {
public:
    Month(int month) {
        std::cout << "-- Month() constructor called for value: " << month << std::endl;
        if ((month < 0) || month > 12)
            throw std::invalid_argument("month");
            // or throw std::out_of_range("month");
        else
           month_ = month;
    }
    ~Month() {
        std::cout << "-- ~Month() destructor called." << std::endl;
    }
    int getMonth()const { return month_; }
private:
    int month_;
};
Run Code Online (Sandbox Code Playgroud)

您创建的实例Month将被堆栈展开机制正确丢弃。永远不会创建实例,因此根本不会调用析构函数。

请参阅现场示例


为了使异常更具信息性,您可以使用以下内容:

        if ((month < 0) || month > 12) {
            throw std::out_of_range("'month' parameter must be in the range of 1-12.");
        }
Run Code Online (Sandbox Code Playgroud)

此外,如果您不喜欢在构造函数的主体中初始化成员变量(就像我经常做的那样)

您可以为有效性检查代码引入一个lambda 表达式

auto month_check = [](int month) {
    if ((month < 0) || month > 12) {
        throw std::out_of_range("'month' parameter must be in the range of 1-12.");
    }
    return month;
};

class Month {
public:
    Month(int month) : month_(month_check(month)) {
        std::cout << "-- Month() constructor called for value: " << month << std::endl;
    }
    ~Month() {
        std::cout << "-- ~Month() destructor called." << std::endl;
    }

    int getMonth()const { return month_; }
private:
    int month_;
};
Run Code Online (Sandbox Code Playgroud)

现场演示