【问题标题】:Complexity of Exceptions异常的复杂性
【发布时间】:2009-09-23 23:44:24
【问题描述】:

我在业余时间玩弄 C++ 代码,致力于构建一个可用于构建 RPG 类型游戏的通用库。它主要关注底层数据结构和功能。我离处理图形还有很长的路要走。

我的问题是:异常应该有多复杂。我有一个看起来像这样的基本例外:

标题:

#include <iostream>
class Exception
{
protected:
   std::string messageM;
public:
   Exception() throw();
   Exception(std::string message) throw();
   Exception(std::string file, int line) throw();
   Exceptoin(Exception & exception) throw();
   virtual ~Exception();

   virtual std::string getMessage(void) throw();
   virtual void setMessage(std::string message) throw();
};

构造函数大多只是一堆“messageM = message”之类的东西。

Exception(std::string, int) 使用 stringstream 将数字转换为字符串。

这太复杂了吗?

我希望平均异常调用类似于:

Exception exception("Message Here");
throw exception;

Exceptoin exception(__FILE__, __LINE__);
throw exception;

另外,在相关说明中,C++ 是否允许您创建异常并同时抛出它。类似于throw(Exception exception("My Message")); 的事情似乎给了我错误。

^_^ 谢谢。

【问题讨论】:

    标签: c++ exception


    【解决方案1】:

    查看关于异常主题的boost documentation,它对设计异常类有一些指导。实际上,您真的应该考虑只使用 boost。

    【讨论】:

    • 我会接受这个建议,然后再看看 boost::exception。
    【解决方案2】:

    异常需要传达的信息并不多:出了什么问题以及为什么会出错。所以你设计的那部分是好的。

    但是,您使用的是std::stringstd::stringstream。这通常被认为是不好的,因为std::string 的构造函数可以在没有内存时抛出异常(并且std::stringstream 调用std::string 的构造函数)。

    所以柯南尝试施法,这会导致异常,因为柯南不是巫师,您创建异常但发现没有可用内存供您的 std::string 使用,因此创建异常会引发例外。此时您有两个问题需要处理(柯南的拼写尝试和没有可用的内存)并且程序无法知道应该首先处理哪个问题。 C++ 用一个简单的解决方案解决了这个问题:程序立即终止。当然,您不会从程序中获得大量信息,也不会清理任何内容(例如,文件不会刷新到磁盘)。

    出于这个原因,std::exception 类使用 char*s。如果你打算走那条路,那么完整的 Monty 并(公开)继承 std::exception 是有意义的。

    至于在没有std::stringstream 的情况下将int 转换为char*,您可以这样做:

    #include <limits>
    #include <cstdio>
    
    char as_string[std::numeric_limits<int>::digits10 + 1];
    if (std::sprintf(as_string, "%d", x) >= 0) {
        // as_string has the converted char* string here ...
    }
    

    我不确定你能从中得到什么。捕获异常的程序部分将负责决定如何处理行号。如果它选择显示行号,则可以预期它会进行转换。如果它想做别的事情,你就做了不必要的工作。如果您处理的不是行号(例如errno 代码),那么异常处理程序甚至可能需要将字符串转换回int(传递给perror(),例如实例)。

    还有两个问题:

    • 不需要 setMessage()。

    throw Exception("what went wrong")

    更常见
    Exception ex;
    ex.setMessage("what went wrong");
    throw ex;
    

    如果你要使用第二个版本,你就不会抛出ex 本身。相反,您会抛出 ex 的副本(请参阅下一点),因此您需要一个复制构造函数。

    • 不需要虚拟析构函数。

    如前所述,无论如何,您都不会通过指向基类的指针来处理异常。但是当你完成它们时,你也不要delete 异常(这是调用虚拟析构函数的时候)。 C++ 需要特殊的异常机制。 throw 将对象分配到特殊的地方(即,在堆栈展开时它不会被销毁的某个地方),以便对象将在您的 catch 块内。这就是throw ex 会创建ex 的副本并抛出副本的原因——ex 将在您到达catch 块时消失。处理完异常后,对象会自动销毁。

    【讨论】:

    • 已经表明 setMessage()<iostream></iostream> 是坏主意。如何以安全的方式将整数转换为字符串,例如使用 __LINE__ 通过异常传递信息时?我想我会接受上面的建议,并对 boost::exception 进行彻底的审视。我对 std::string 的主要兴趣在于,当对象向上传递到堆栈时,添加信息变得多么容易。消息 = "添加信息\n" + 消息。 ^_^
    • 其实还是需要虚拟析构函数的。见boost.org/doc/libs/1_40_0/libs/exception/doc/…
    • 虚拟继承与虚拟析构函数、虚拟方法或通过指向其基类的指针 (en.wikipedia.org/wiki/Virtual_inheritance) 处理对象不同。异常机制将为您正确处理清理。
    【解决方案3】:

    虽然看起来不太复杂,但您可以考虑

    • 使用/派生自合适的std 异常并使用stds 约定
    • 扔掉 setMessage() - 构造函数对于throw Exception("foo"); 来说已经足够了
    • 丢弃虚拟(您通常何时希望将指针传递给周围的异常或向上转换它们?)

    澄清:
    当您想要捕获异常基类 (background) 时,至少基异常的析构函数应该是虚拟的,以避免在 catch(...) {} 上出现歧义并导致不必要的命中。
    谢谢马丁。

    【讨论】:

    • 我会遵循 std 异常示例并在析构函数上保持虚拟。
    【解决方案4】:

    只回答问题的最后一部分:

    此外,在相关说明中,C++ 是否允许您创建异常并 同时扔。类似于 throw(Exception 异常(“我的消息”));似乎给了我错误。

    试试

    throw Exception("My message");
    

    【讨论】:

      【解决方案5】:

      处理异常应该有多复杂'问题的一部分:

      异常代码与任何其他代码没有什么不同。它应该像必须那样复杂,仅此而已。

      【讨论】:

        【解决方案6】:

        你也可以在异常上创建类方法(静态方法)。

        例如

        MyException::raise("my message")

        当我想检查某个错误情况时,我经常会创建一个静态方法,如果是,则抛出异常。

        例如BadArgument::Check("mymethod", "paramName", paramvalue)

        【讨论】:

        • 你真的可以使用“throw”作为函数名吗?
        • @Ropez,不,你不是。这是一个关键字,所以没有骰子。
        • “raise”是一个自然的选择。
        • 好点,将编辑!
        猜你喜欢
        • 1970-01-01
        • 2014-04-04
        • 2014-03-12
        • 2016-08-04
        • 2020-10-11
        • 2013-07-24
        • 2021-03-25
        • 1970-01-01
        相关资源
        最近更新 更多