我发现C ++标准函数在出现异常时表现出非常不同的行为。这似乎与其“尝试/抛出/捕获”的兜售相矛盾。任何人都可以请简要解释这些选择背后的C ++设计师的推理是什么?
什么都不做,例如,当它是空的时候尝试pop()一个堆栈(而不是抛出一个range_error),执行sqrt(-1)(而不是抛出一个domain_error)
返回零指针:例如,当做非法指针向下转换时(有趣的是,做非法引用向下转换会抛出bad_cast)
抛出异常,但这似乎是少数函数,例如substr()
让用户选择是否抛出异常,例如,new()会在内存不足时抛出bad_alloc(),但您也可以选择(nothrow)作为new()的选项。
答案 0 :(得分:3)
C ++库函数的大部分行为都可以用一般的C ++哲学解释“你不用付出你不使用的东西”。这意味着当您正确使用它时,任何特定的构造都不应产生任何不必要的开销。
可选地,可能存在更昂贵的已检查版本,例如std::vector::at()
,但是由您决定是否使用它们。
stack::pop()
和sqrt()
的示例显示了这种理念:为了在错误上抛出异常,您总是必须检查调用是否有效。如果您已经知道您的呼叫将成功,那么此检查是不必要的,因此这些功能中没有强制检查。如果你想要一张支票,你可以自己写一个。
默认new
略有不同,因为它包含了调用new_handler
的工具,因此无论如何都要进行检查。 (回想一下,如果你实际抛出异常只是一个例外,那么这个方面并不那么重要。)如果你想,你总是可以将你自己的全局operator new()
替换成一个字面上只是将参数转发给malloc()
。 (这当然会使得使用默认的new
表达式变得不安全,因为你现在无法检查你可以在返回的指针处构造一个对象。所以你最终会自己编写一个检查并使用placement-new,这几乎就是nothrow-version所做的。)
答案 1 :(得分:2)
返回零指针:例如,当做非法指针向下转换时(有趣的是,做非法引用向下转换会抛出bad_cast)
dynamic_cast
提供了一种检查演员表有效性的方法。它绝不是一个例外。使用指针,强制转换会返回一个NULL
,因为抛出一个异常将是一个开销,同样可以通过返回一个NULL
来实现,但是参考不能引用是NULL
,除了抛出异常之外别无选择,在这种情况下没有其他方法可以将结果返回给用户。
让用户选择是否抛出异常,例如,new()会在内存不足时抛出bad_alloc(),但你也可以选择(nothrow)作为new()的选项。
很久以前new
刚刚返回NULL,就像malloc
一样,但后来它被标准化为抛出bad_alloc
异常,这意味着以前使用{编写的所有代码{1}}必须在很大程度上修改以处理异常,为了避免这种情况并保持兼容性,引入了新版本的新版本。
答案 2 :(得分:2)
返回null的指针向下转换是一种更简单,更快速的方法来测试给定对象是否属于给定的子类。即你可以编写像if (dynamic_cast<A*>(v) || dynamic_cast<B*>(v))
或if (A* a = dynamtic_cast<A*>(v)) doStuffWith(a);
这样的东西,这些东西在例外情况下会很麻烦。在这里,你实际上期望强制转换失败,而异常因其性质而异常,因为它们在程序的正常执行期间很少被抛出。
在其他情况下,出于性能原因,可能会省略对错误值的显式检查。 C ++应该是高效的,而不是试图阻止一个人在腿上射击自己。
答案 3 :(得分:1)
这大部分取决于是否有足够的适当回报值来表达“失败”条件以及这些值是否方便。
std::stack<>::pop()
不会抛出错误,因为它没有错误值,并且简化了调用代码。尝试弹出空堆栈可能不是应用程序逻辑错误。
std::sqrt()
具有适当的值(不是数字),它恰好为此目的包含在浮点表示中。它还具有通过其他计算干净传播的好处。
dynamic_cast<>
返回一个空指针,指示失败的强制转换,因为空指针已经是表示“无指向”的标准方式。但是,没有引用的等效项,因此必须抛出异常,因为返回值不能用于表示错误。
相反,std::string::substr()
不能返回空字符串来表示失败,因为空字符串是所有字符串的有效子字符串。
new
抛出std::bad_alloc
或者似乎没有历史根源,但是,就像指针上的dynamic_cast<>
一样,某些代码可能会尝试替代不支付异常处理
答案 4 :(得分:1)
c ++标准库旨在提高效率,尽可能避免不必要的运行时检查。
1包含对非常小/快的方法的前提条件的违反,检查这些前提条件很可能比执行方法本身花费更长时间(pop很可能是一堆简单类型的单个减量)。
2 dynamic_cast检查并将指定的指针强制转换为兼容类型。没有单独的方法只检查演员是否可能,因为它必须做与演员相同的工作。由于c ++没有提供单独的方法来仅检查转换是否可行,我们必须预期它可能会失败并且它在失败时使用它具有良好的错误值(NULL)。引用版本必须抛出异常,因为它不能返回错误值。
3 substr保证例外,这可能有两个原因。一:substr方法比1中提到的方法复杂得多,速度慢,因此检查前置条件的开销可以忽略不计。二:字符串处理是安全漏洞的最大贡献者之一,因为您最有可能处理用户输入,检查溢出或越界访问是保持进程安全/稳定所必需的。 c-library提供快速,未检查和不安全的方法来为需要速度的人操纵字符串。
4 new必须检查它是否可以返回地址或在两种情况下都失败,因为大多数应用程序的内存耗尽是出乎意料的,异常是合理的。但是,您可以在使用其一小部分功能时编写c ++,并且许多项目不使用异常,因为使您的代码异常安全很难(特别是如果您使用非第三方库),因为new是c ++的核心部分。无异常的实现变得必要。