【问题标题】:How to prevent the constructor from creating an object when an exception is thrown抛出异常时如何防止构造函数创建对象
【发布时间】:2017-12-04 19:58:18
【问题描述】:

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

在下面的示例中,我创建了一个 Month() 类,其 int month_ 属性的合法值在 1 到 12 的范围内。我实例化了 December,或 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;
}

【问题讨论】:

  • 不要捕获异常。让它从构造函数中抛出。
  • if ((month &lt; 0) || month &gt; 12) 允许 13 个有效值,012。假设您使用的是零索引,那么if (month &lt; 0 || month &gt;= 12) 可能是要走的路。或者,如果您想要传统的序数索引,if (month &lt;= 0 || month &gt; 12)
  • 好眼纳特!!!!

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


【解决方案1】:

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

要点:

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

【讨论】:

  • 几年没用过C++了,所以上面的语法不是100%。如果我错字了,请随时编辑。
【解决方案2】:

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

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

From iso/cpp source 1

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

From iso/cpp source 2

【讨论】:

  • 第一句警告:如果你很傻,memberPointer = new ButtLoadOfData;,如果抛出异常,没有简单的方法delete memberPointer;。将memberPointer 声明为智能指针将解决此问题。
【解决方案3】:

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

您只是(重新)抛出异常,而不是捕获它,我建议抛出 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

【讨论】:

  • std::out_of_range 在语义上比 std::invalid_argument 更符合 IMO。
  • @Rakete 当然,这就是我提到它的原因。
  • 好吧,我什至不会考虑std::invalid_argument 的可能性,因为std::out_of_range 是一个更好的匹配,但这只是我的意见:)
  • @Rakete 我也最终决定选择this
  • @Rakete 甚至是lambda version。不过,将 lambda 与初始化列表内联会很好。
【解决方案4】:

您必须向 main 抛出异常,关于构造函数的消息应该在块 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

【讨论】:

    【解决方案5】:

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

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

    如果你抓住它,就好像异常从未发生过一样。你抓住了它,所以它不再进入调用堆栈。所以对象就创建好了,因为在任何人看来,构造函数都没有抛出异常。

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        猜你喜欢
        • 2016-07-18
        • 2012-04-23
        • 2013-03-18
        • 1970-01-01
        • 2013-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多