温习一下C++的一些重要概念(上)
关于命名空间与头文件的概念,相信你对此已经理解得很深刻了,在此不再赘述(可翻阅《C++ Primer Plus》的相关章节)。我们来重点回顾一下以下知识点:
- 差错和异常处理
- 可被调用的对象
- 并发与多线程
- 初识分配器
本节我们主要学习差错和异常处理,想了解try{}catch{}详细用法的朋友请仔细阅读本部分内容。
1.差错和异常处理
a) 标准异常类
所有被语言本身或标准库抛出的异常,都派生自基类exception,定义于<exception>。它是若干标准异常类的基类。这些标准异常类可分为三组:
- 语言本身支持的异常
- C++标准库发出的异常
- 程序作用域之外发出的异常(例如,资源不足)
针对语言支持而设计的异常类
此类异常用以支撑某些语言特性,所以,从某种角度说它们不是标准库的一部分,而是核心语言的一部分。以下操作如果失败会抛出该类异常:
- 运行期间“动态类型转换”失败时,dynamic_cast会抛出bad_cast异常,此异常定义于<typeinfo>。
- 运行期间类型辨识(RTT1)过程中,如果交给typeid操作符的实参为0或空指针,typeid操作符会抛出bad_typeid异常,此异常定义于<typeinfo>。
- 定义于<exception>内的bad_exception异常被用来处理非预期的异常。它可以由函数unexpected()抛出,该函数会在“某个函数抛出的异常不在异常明细内”时被调用。[注:]c++11不再鼓励使用异常明细。
针对逻辑差错而设计的异常类
针对逻辑差错而设计的异常类派生自logic_error。逻辑差错是指可在程序中避免的错误,包括“违背逻辑先决条件”或“违反class不变性”等。c++标准库提供了5种针对逻辑差错的异常类:
- invalid_argument表示无效实参,例如,将bitset以char初始化。
- length_error指出某个行为可能超过最大允许的大小,例如,对某个字符串附加太多字符。
- out_of_range指出实参值不在预期范围内,例如,array或string索引越界。
- domain_error指出领域范围内的错误。
- future_error指出当使用非同步系统调用时发生的逻辑差错。
针对逻辑差错而设计的异常类通常被定义于<stdexcept>内,但是class future_error被定义于<future>。
针对运行期差错而设计的异常类
派生自runtime_error的异常都被用来指出“不在程序作用域内且不容易回避”的事件。
- range_error指出内部计算时发生区间错误。例如,在“wide string”和“byte string”之间转换时可能发生该异常。
- overflow_error指出算术运算时发生上溢。例如,bitset被传入一个整数值会抛出该异常。
- underflow_error指出算术运算发生下溢。
- system_error指出因底层操作系统而发生的差错。例如,c++标准库可在并发环境中抛出该异常。
- bad_alloc是最重要的一个异常。只要new失败,定义于<new>中的bad_alloc就会被抛出。如果传给new的大小小于0或超过编译器定义的极限,派生自bad_alloc的bad_array_new_length就会被抛出。
- bad_weak_ptr会在根据shared pointer创建weak pointer失败时被抛出,它被定义于<memory>头文件中。
- bad_function_call会在function外覆物被调用但其实没有目标时被抛出,它被定义于<function>头文件中。
- 针对标准库的I/O部分,有一个特别的异常类ios_base::failure,它被定义于<ios>头文件中。此类异常可能会在stream发生错误或遇上end-of-file而改变其状态时被抛出。c++11起该类派生自system_error,c++11之前派生自exception。
针对运行期差错而设计的异常类通常被定义于<stdexcept>内,但是class system_error被定义于<system_error>。
由标准库抛出的异常类
如前所述,几乎所有异常类都有可能被C++标准库抛出。比如,bad_alloc异常。
此外,由于标准库可能用到应用程序开发人员所写的代码,所以也可能间接抛出任何异常。
标准库的任何具体实现都有可能提供更多异常类。然而,使用这些非标准类将导致程序难以移植。所以,最好只使用标准异常类。
此外,由于标准库可能用到应用程序开发人员所写的代码,所以也可能间接抛出任何异常。
标准库的任何具体实现都有可能提供更多异常类。然而,使用这些非标准类将导致程序难以移植。所以,最好只使用标准异常类。
异常类的头文件
为了能够处理标准库可能抛出的所有异常,必须包含:
#include <exception> // for classes exception and bad_exception
#include <stdexcept> // for most logic and runtime error classes
#include <system_error> // for system errors(since c++11)
#include <new> // for out-of-memory exceptions
#include <ios> // for I/O exceptions
#include <future> // for errors with async() and futures(since c++11)
#include <typeinfo> // for bad_cast and bad_typeid
b) 异常类成员
为了在catch子句中处理异常,你必须采用该异常类的接口。所有标准异常都提供了what(),某些异常类还提供了code()。
成员函数what()
对所有标准异常而言,可用来获取类型以外的附加信息的唯一成员函数就是what(),它返回一个以'\0'结尾的字符串。
namespace stdexcept
{
class exception
{
public:
virtual const char* what() const noexcept;
// ...
}
}
what()返回是string内容由编译器实现定义。注:该string有可能是以'\0'结尾的宽字节字符串,可被转换为wstring。what()返回的字符串在其所属异常对象析构后,或被赋予新值后,就不再有效了。
差错码和差错状态
异常类system_error和future_error还拥有一个额外的成员可以取得详细信息。然而在进入详细信息之前,我们必须先了解“差错码”和“差错状态”的差别:
- 差错码 是一种用来封装差错码值的轻型对象,差错码值可由编译器实现指定,不过某些差错码是标准化的。
- 差错状态 是一种提供差错描述的对象。
if (ec == std::errc::invalid_argument) {...} //检查差错状态
if (ec == std::future_errc::no_state) {...} //检查差错码
为了能够处理差错码和差错状态,可提供额外的非虚成员函数code():
namespace std
{
class system_error : public runtime_error
{
public:
virtual const char* what() const noexcept;
const error_code& code() const noexcept;
// ...
}
class future_errc : public logic_error
{
public:
virtual const char* what() const noexcept;
const error_code& code() const noexcept;
// ...
}
class error_code
{
public:
const error_category& category() const noexcept;
int value() const noexcept;
string message() const;
explicit operator bool() const noexcept;
error_condition default_error_condition() const noexcept;
// ...
}
}
我们可以这样处理异常:
c) 以exception_ptr传递异常
C++11起,C++标准库提供了一个能力:将异常存储于类型为exception_ptr的对象中,稍后才在其他情境中处理它们:
#include <exception>
std::exception_ptr eptr; // 默认为nullptr.
void foo()
{
try
{
throw ...;
}
catch(...)
{
eptr = std::current_exception(); // 保存当前异常到eptr中
}
}
void bar()
{
if(eptr != nullptr)
{
std::rethrow_exception(eptr); // 重新抛出被保存的异常
}
}
以上特性在线程之间传递异常时特别有用。
d) 抛出异常
所有提供what()接口的逻辑差错和运行期差错标准异常,都只有一个构造函数接受std::string和一个构造函数接受const char*参数(C++11新增)。例如:
namespace std
{
class logic_error : public exception
{
public:
explicit logic_error(const string& whatString);
explicit logic_error(const char* whatString); // since c++11
// ...
}
}
namespace std
{
class system_error : public runtime_error
{
public:
system_error(error_code ec, const string& whatString);
system_error(error_code ec, const char* whatString); // since c++11
system_error(error_code ec);
system_error(int ev, const error_category& ecat, const string& whatString);
system_error(int ev, const error_category& ecat, const char* whatString);
// ...
}
}
抛出异常是很简单的一件事:
throw std::out_of_range("out_of_range(somewhere, somehow)");
throw std::system_error(std::make_error_code(std::errc::invalid_argument), "This is system_error!");
e) 自定义异常类
我们还可以建立一个自己的异常类,使其直接或间接派生自exception基类。注:必须确保what()机制和code()机制能正常运作(提示:基类的what()是虚函数)。
<未完,转下一节>