抛出非异常对象

15

C++允许 throw 任何类型的对象,从 exceptionstring 甚至 int

但我从未见过除了 exception 之外其他类型对象的真实应用场景中使用 throw

我的问题是,throwexception 对象的应用场景是什么?


1
例如,您的应用程序可能已经具有一些错误描述机制,并且您无法从异常中继承它。 - Alex Telishev
实际上,除了抛出“异常”之外,几乎没有其他应用程序。 - John Dibling
@AlexTelishev 那么,在我的看法中,将_'某种错误描述机制'_封装在由std::exception派生的异常中可能是更好的想法,而不是直接捕获枚举错误码或类似的。 - πάντα ῥεῖ
7个回答

12

从实用角度来看,几乎没有任何应用程序可以抛出 stringint 或其他非源自 std::exception 的内容。

这不是因为没有指示这样做,而是因为有反指示表明你不应该这样做。

有两个主要原因,为什么你不想抛出任何不源自 std::exception 的东西:

  1. 异常安全。 如果你抛出了一个 std::string,并且该 string 的构造或复制引发另一个异常,那么将调用 terminate,你的进程将停止运行。你再也没有机会捕获那个std::string
  2. 可用性。 抛出派生自 std::exception 的异常,使得能够以通用方式catch (const std::exception&)。如果你抛出其他东西,你需要为该情况编写一个catch

这里有一个关于异常的好讨论。


1 几乎没有任何应用程序可以抛出......: 每个规则都有例外,但即使承认这一点,我从未见过一个合法的抛出非 std::exception 派生类的情况。


我认为像 throw std::string("oops") 这样的语句不会导致程序终止。如果构造函数抛出异常,那么 throw-expression 就不会被评估,因此没有第二个 throw,就像 throw f(std::string("oops")) 不会调用 f 一样。 - aschepler
它至少会导致抛出错误的异常。 - John Dibling
@galop1n:我并没有说你不能这么做。我只是说通常情况下你不应该这样做。 - John Dibling
几乎没有应用程序会模拟硬异常(例如int),这将绕过所有catch子句并结束于main(),您怎么看? - marcinj
@marcin_j:那可能是一个例外,不是双关语。虽然相当罕见。 - John Dibling
显示剩余2条评论

5

这更像是一种黑客技巧而非语言特性,你可以抛出一个对象然后捕获它,以强制函数“返回”与其正常返回类型不同的内容。

int aFunc()
{
    throw foo(); // if you catch that foo, you 've imitated returning foo
    return 0; // ok just an int
}

当然,这将是一个可怕的设计选择,违反了C++提供的类型安全性,但是假设你有一个在庞大代码库中广泛使用的函数,并且你想尝试一些更改(涉及更改返回类型),那么这将是在实际实现更改之前尝试某些内容的肮脏方式(并搜索整个代码库进行更改)。编辑:你应该仔细阅读帖子。我说过“可怕的设计选择”、“违反了C++提供的类型安全性”和“在实际实现更改之前”。如果这还不足以作为警告,我认为这些评论或者踩也不会起到作用。另一方面,如果要尝试更改在代码库中被使用了666次的函数的返回类型,而代码库有6e06行,则上传到版本控制系统后发现不是你想要的,而且会多次打破其他平台上的开发人员的编译,那么你会想知道是否有捷径吗?在实际实现更改并将其发布到代码库之前,您会使用它吗?
即使对这些问题的回答是“否”,我认为这篇文章是关于探索可能性的,仅仅提到一个可能性本身并不是“邪恶”的。我个人听过Bjarne的演讲http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style上提到过这个可能性,但之后也说过不要使用这样的东西。

1
如果你捕获了那个foo,你就模仿了返回foo。不要这样滥用异常! - πάντα ῥεῖ
3
非常糟糕的建议。例外情况是为了特殊情况而设的,而不是用于常规流程控制。 - Captain Obvlious
3
@CaptainObvlious 实际上这个建议非常好。他清楚地指出这不应该被做。为什么要对指出技术上可能使用但明确表示这是一个坏主意的人进行负面评价?至少它给了发帖者一个机会,让他在遇到滥用时能够识别出来。我曾经看到过使用这种 hack 的生产代码。请参见 http://www.drdobbs.com/cpp/twisting-the-rtti-system-for-safe-dynami/229401004 以获取更完整的示例。 - marack
@marack 你说得对,我漏掉了他帖子的那部分。 - Captain Obvlious

1
你可以抛出任何东西,但它总是会作为异常被抛出。标准库使用从std::exception继承的类,这是一种设计选择。
如果你觉得抛出枚举已经足够了,为什么要强制抛出一个对象呢?

1
那么 throw 1; 能够与 catch(std::exception) 配合使用吗?因为 "You throw whatever you wants, it will always be an exception as you throw it." 这句话的意思是无论你抛出什么,它都会被视为异常。 - Luchian Grigore
你抛出的对象是一个异常,这并不意味着它的类型是std::exception。你必须捕获API调用文档中记录的正确类型。 - galop1n
1
这里的含义是没有理由不抛出你想要抛出的任何东西。我不同意这个含义。除了从 std::exception 派生出来的东西,几乎没有什么好的理由可以抛出其他东西。 - John Dibling
@Sahsahae 虽然我不记得这个问题的确切背景了,但在我的问题/评论中从未暗示过要使用 catch-all 网。我也可以争辩说,生命中还有比挑剔 8 岁的评论更重要的事情,但那是另一个讨论。:) 祝你有美好的一天,伙计! - Luchian Grigore
@Sahsahae 我不明白你的意思。我没有回答这个问题,我只是询问回答者需要澄清的内容,并且我也没有暗示你应该捕获一个通用的 std::exception。你假设我做出了一个我没有做出的陈述。 - Luchian Grigore
显示剩余2条评论

1

我不确定是什么促使了这个问题,因为在这种情况下,你可以自由选择“应用程序”。

对象异常的目的是指示异常发生的事实,并通常携带一些特定于异常的信息从抛出者到处理者。如果某种类型足以满足您的应用程序的要求,则可以将该类型用作“异常”类型。您可以抛出int值、std::string值和任何其他值,如果您愿意。完全取决于您。

如果您想从抛出者传递给处理者的只是一个int值,则类型int将作为“异常”类型服务。就是这样。

C++不对您可以抛出的类型施加任何特定要求。因此,真正没有所谓的“异常”类型或“非异常”类型。什么是“异常类型”和什么不是由您在代码中决定。只要您知道如何捕获它并解释您捕获的内容,您可以抛出任何东西。

使用类类型来表示异常的第一个好处是类类型是自由可定义的,即您可以轻松定义任意数量的异常类型。然后,您可以使用许多独立、不干扰的异常“流”,它们仅抛出和捕获其各自类型的异常。
使用类类型来表示异常的第二个好处是类类型易于扩展。即,在任何时刻,您都可以向您的异常类型添加附加的“载荷”信息,并且该载荷将从抛出异常的点传递到捕获和处理异常的点。
如果您对这些好处不感兴趣,您可以简单地抛出和捕获类型为int的值来表示您的异常。这将起作用。但我几乎可以肯定,您很快就会遇到这种方法在维护和扩展性方面的限制。

这个问题并不是技术上的,因为MBZ提出的不是是否可以抛出除了std::exception派生对象之外的其他东西,而是何时应该抛出。至少我是这样理解这个问题的。如果你断言有合法的情况可以抛出不派生自exception的东西,你能详细说明一下吗?说实话,“只要你知道你在做什么就可以”听起来有点模糊。 - John Dibling

1
一般来说,任何被抛出的对象都应该派生自std::exception。当然也有例外情况,最明显的是:我曾经参与过的项目更倾向于抛出一个int而不是调用exit()。当然,这只在main遵循捕获int并返回它的约定时才起作用。(这样做的优点是,所有局部变量都将被析构,而如果你调用exit(),则不会。)
而且,在小型一次性测试程序中,我通常只会抛出一个字符串字面量(可以用char const*捕获),这样可以立即输出。(这不是任何生产代码的好方法,但当你为了学习而进行试验时,这似乎是可以接受的。)

0

正如 @galop1n 所回答的,你可以抛出任何你喜欢的东西。但是

  1. 通常他们会抛出普通数据对象而不是指针,以确保编译器可以自动管理内存,所以 throw(type()) ---> catch(type const& e)
  2. 通常他们会抛出从 std::exception 继承的东西,以满足那些希望用 catch(std::exception const& e) 捕获所有可记录日志的东西的期望。尽管你可能会反驳说,他们不应该捕获任何他们不知道是什么的东西。

我非常清楚这个词的含义!我一直在问“谁”,你能详细说明一下吗?是标准库开发人员吗? - πάντα ῥεῖ
@non-latin-nick - 为什么不发表你的答案,获得应有的社区认可,展示你的英语和深厚的专业知识呢? - bobah
旁注:_@non-latin-nick_ 很容易复制 / 旁注。我不会回答一个问题,这个问题主要吸引基于观点的答案,好吗? - πάντα ῥεῖ

0

我不同意一般观点认为只能抛出 std::exception 的派生类。当然,我可以认同抛出具有复杂数据或行为的对象可能非常棘手,应该谨慎行事。

然而,这并不意味着抛出类型的继承层次结构受到任何限制,也不意味着您的应用程序使用堆栈展开的方式(或原因)受到任何限制。而 throw 操作仅仅是这样一个干净而明确定义的方法:在达到定义点(匹配的 catch)之前卸载堆栈,而无需涉及中间帧中的显式处理程序

当然,有合法的抛出用例,并不意味着发生错误或任何异常情况。例如,考虑一个简单的语言解释器,其中主机语言的堆栈对应于客户语言的堆栈。为了在客户语言中实现交错并行性,当客户操作阻塞(或产生值)时,堆栈展开正是您需要执行的操作。您想卸载到并行性的起始点,并尝试并行分支中的下一个操作。这种情况既不是异常情况,也不是任何类型的错误。

请注意,关键字被称为throw/catch,而exception概念并未以任何方式嵌入语言中。这是其中之一的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接