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

时间:2017-06-30 19:21:50

标签: c++ c++11 constructor exception-handling try-catch

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

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

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

输出

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

最小,完整且可验证的示例

#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;
}

6 个答案:

答案 0 :(得分: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

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

答案 1 :(得分:10)

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

你可以在catch中抛出一个新的异常,然后可以在这个范围之外看到,这样就不会调用析构函数。

答案 2 :(得分:4)

  

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

你只是(重新)抛出异常,而不是抓住它,我建议抛出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_;
};

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

查看Live Example

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

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

此外,如果你不喜欢在构造函数体中初始化成员变量(正如我所做的那样)

您可以为有效性检查代码引入 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_;
};

Live Demo

答案 3 :(得分:1)

你必须抛出异常例如main,关于构造函数的消息应该在块try中。 所以... 2.创建对象,当创建对象时,异常throw 100并在main中处理。我认为,这是很多可能性中的一个。

#include <iostream>
#include <exception>
#include <iostream>
#include <string>

class Month {
public:
    Month(int month) {
        try {
            if ((month < 0) || month > 12) throw 100;
            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_;
};

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() {
    try {
        makeMonths();
        std::cout << "Press any key to exit"; std::cin.get();
    }
    catch (...)
    {
        std::cout << "exception" << std::endl;
    }

    return 0;
}

<强>输出:

-- Month() constructor called for value: 2
-- Month() constructor called for value: 6
-- ~Month() destructor called.
-- ~Month() destructor called.
exception

答案 4 :(得分:1)

您应该从构造函数中抛出异常并将其捕获到除构造函数之外的代码中,例如尝试创建对象的代码。

  

如果构造函数通过抛出异常而结束,则清除与对象本身关联的内存 - 没有内存泄漏

From iso/cpp source 1

  

如果构造函数抛出异常,则不会运行该对象的析构函数。

From iso/cpp source 2

答案 5 :(得分:1)

如果输入无效,您可以使用工厂方法模式来避免调用构造函数。

要点:

  1. 创建一个名为static的{​​{1}}方法。

  2. 外部实体调用.New()而不是构造函数。

  3. .New()验证输入。

    • 如果输入正常,则调用构造函数并返回结果。

    • 否则,它会抛出异常。或者,您可以返回.New(),或返回非null默认值。

  4. null