如何防止构造函数在引发异常时创建对象

How to prevent the constructor from creating an object when an exception is thrown

本文关键字:异常 创建对象 何防止 构造函数      更新时间:2023-10-16

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

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

如何在引发异常时中止类实例的创建?

输出

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

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

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

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

如果从构造函数中删除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,在那里它也没有被捕获,因此程序异常终止。

默认情况下,在构造函数中引发异常应阻止调用析构函数。但是,您正在捕获异常并处理它。

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

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

你只是(重新(抛出异常,而不是捕获它,我建议抛出一个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实例将使用堆栈展开机制正确丢弃。永远不会创建实例,因此根本不调用析构函数。

查看实时示例


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

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

现场演示

可以使用工厂方法模式来避免在输入无效时调用构造函数。

要点:

  1. 创建一个名为.New()或其他东西的static方法。

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

  3. .New()验证输入。

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

    • 否则,它将引发异常。 或者,您可以返回null或返回非null默认值。


#include <iostream>
#include <string>
class Month
{
public:
static Month New(int month)
{
std::cout << "-- Month.New() factory method called for value: " << month << std::endl;
if (month < 0 || month >= 12)
{
std::cout << "-- Month.New() factory method found that month was invalid; throwing exception" << month << std::endl;
throw /*exception type here*/;
}
return Month(month);
}
~Month()
{
std::cout << "-- ~Month() destructor called." << std::endl;
}
int getMonth()const { return month_; }
private:
int month_;
Month(int month)
{
month_ = 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;
}

例如,您必须将异常抛出到 main,关于构造函数的消息 schould 在块try中。 所以。。。 2. 对象创建以及何时 3.正在创建的对象,异常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

您应该从构造函数中抛出异常,并在构造函数以外的代码中捕获它,例如尝试创建对象的地方。

如果构造函数通过抛出异常完成,则与对象本身关联的内存将被清理 — 没有内存泄漏

来自 iso/cpp 来源 1

如果构造函数引发异常,则不会运行对象的析构函数。

来自 iso/cpp 源 2