std::exception
类?它们有什么好处?我的提问原因是这样的:try
{
throw std::string("boom");
}
catch (std::string str)
{
std::cout << str << std::endl;
}
代码可以正常工作。如果需要的话,我可以自己创建轻量级的“异常”类型。那为什么还要费心使用 std::exception
?
std::exception
类?它们有什么好处吗?它提供了一个通用和一致的接口来处理标准库抛出的异常。所有标准库生成的异常都继承自std::exception
。请注意,标准库API可能会抛出许多不同类型的异常,举几个例子如下:std::bad_alloc
, std::bad_cast
, std::bad_exception
, std::bad_typeid
, std::logic_error
, std::runtime_error
, std::bad_weak_ptr | C++11
, std::bad_function_call | C++11
, std::ios_base::failure | C++11
, std::bad_variant_access | C++17
等等。等等...std::exception
是所有这些异常的基类。
std::exception
呢?如果您需要自定义异常类,请自行创建。但是std::exception
使您的工作更加轻松,因为它已经提供了许多良好的异常类应该具有的功能。它为您提供了从中派生和覆盖必要函数(特别是std::exception::what()
)以实现类功能的便利。std::exception
处理程序,
what
并不是一个好的函数的例子。我对它返回C字符串的事实表示异议。天哪,都已经2013年了! - Matthieu M.C++标准为何会费心发明std::exception类?有什么好处吗?
拥有不同的异常类型允许你捕捉特定类型的错误。从一个共同的基类派生异常允许在捕获更通用或特定的错误时进行粒度控制。
在C++中,已经存在一个现有的类型系统,因此在语言中可以显式创建所需类型的异常时,标准化错误字符串是不必要的。
std::exception 及其派生类存在的两个主要原因:
标准库必须具有某种异常层次结构以在异常情况下抛出。始终抛出std::string是不合适的,因为您没有清晰的方式来针对特定类型的错误。
为库供应商提供可扩展的基于类的接口,以抛出最基本的错误类型并为用户提供通用回退。您可能希望提供比简单的what()字符串更多的错误元数据,以便捕获您的错误的人可以更智能地从中恢复。
同时,std::exception作为一个公共基础允许一个常规的catchall,不像...那样包罗万象,如果用户只关心该错误消息。
如果你所做的一切都是打印并退出,那么这并不重要,但你可以使用从std::exception继承的std::runtime_error以方便捕获。
如果您从 std::runtime_error 继承并使用自定义错误类型,那么您可以在不重写 catch 块的情况下添加错误元数据!相反,如果您改变了错误处理设计,则必须重写所有的 std::string catches,因为您不能安全地从 std::string 继承。这不是一项前瞻性的设计决策。稍后,如果需要,我可以自己制作轻量级的“异常”类型。那么我为什么要费心去处理std::exception呢?
#include <vector>
#include <iostream>
#include <functional>
#include <stdexcept>
#include <bitset>
#include <string>
struct Base1 {
virtual ~Base1(){}
};
struct Base2 {
virtual ~Base2(){}
};
class Class1 : public Base1 {};
class Class2 : public Base2 {};
class CustomException : public std::runtime_error {
public:
explicit CustomException(const std::string& what_arg, int errorCode):
std::runtime_error(what_arg),
errorCode(errorCode){
}
int whatErrorCode() const {
return errorCode;
}
private:
int errorCode;
};
void tryWrap(typename std::function<void()> f){
try {
f();
} catch(CustomException &e) {
std::cout << "Custom Exception: " << e.what() << " Error Code: " << e.whatErrorCode() << std::endl;
} catch(std::out_of_range &e) {
std::cout << "Range exception: " << e.what() << std::endl;
} catch(std::bad_cast &e) {
std::cout << "Cast exception: " << e.what() << std::endl;
} catch(std::exception &e) {
std::cout << "General exception: " << e.what() << std::endl;
} catch(...) {
std::cout << "What just happened?" << std::endl;
}
}
int main(){
Class1 a;
Class2 b;
std::vector<Class2> values;
tryWrap([](){
throw CustomException("My exception with an additional error code!", 42);
});
tryWrap([&](){
values.at(10);
});
tryWrap([&](){
Class2 c = dynamic_cast<Class2&>(a);
});
tryWrap([&](){
values.push_back(dynamic_cast<Class2&>(a));
values.at(1);
});
tryWrap([](){
std::bitset<5> mybitset (std::string("01234"));
});
tryWrap([](){
throw 5;
});
}
Custom Exception: My exception with an additional error code! Error Code: 42
Range exception: vector::_M_range_check
Cast exception: std::bad_cast
Cast exception: std::bad_cast
General exception: bitset::_M_copy_from_ptr
What just happened?
仅仅因为一个6行的玩具示例中某个东西“运行良好”,并不意味着它在真实代码中是可扩展或可维护的。
考虑一下这个函数:
template<typename T>
std::string convert(const T& t)
{
return boost:lexical_cast<std::string>(t);
}
如果字符串的内存无法分配,则可能会抛出bad_alloc
,或者如果转换失败,则可能会抛出bad_cast
。
调用此函数的人可能希望处理转换失败的情况,这表明输入有误但不是致命错误,但不想处理内存不足的情况,因为他们无法对其进行任何处理,所以让异常向上传播。在C++中非常容易实现,例如:
std::string s;
try {
s = convert(val);
} catch (const std::bad_cast& e) {
s = "failed";
}
std::string
抛出,那么代码将会是:std::string s;
try {
s = convert(val);
} catch (const std::string& e) {
if (e.find("bad_cast") != std::string::npos)
s = "failed";
else
throw;
}
std::exception
,那么我可以通过const std::exception&
来捕获它们,而不需要知道(或关心)具体的类型。std::exception
(如果你想避免使用标准库异常)。
但是如果你的类将被其他人(程序员)使用,他们如何处理异常呢?
如果你的类throws
一个描述错误的string
,那么这并没有什么帮助,因为你的类的消费者更喜欢一种更标准的方式(捕获异常对象并查询它的方法)来处理exception
,而不是捕获一个字符串。
此外,您可以通过捕获exception
对象来捕获标准库抛出的异常。
您可以重写exception类的what
方法,以提供有关错误的更多信息。
你需要多种类型的异常才能够区分它们 -- 有些异常应该被处理,而其他异常则不需要。
它们需要有一个共同的基类,以便为用户提供显示合理信息的手段,而不必知道程序可能抛出的所有异常类型(使用外部闭源库时是不可能的)。