try 块

来自cppreference.com
< cpp‎ | language

将一或多个异常处理块(catch 子句)与复合语句关联。

语法

try 复合语句 处理块序列

其中 处理块序列 是一或多个 处理块 的序列,它有下列语法:

catch ( 属性(可选) 类型说明符序列 声明符 ) 复合语句 (1)
catch ( 属性(可选) 类型说明符序列 抽象声明符(可选) ) 复合语句 (2)
catch ( ... ) 复合语句 (3)
复合语句 - 花括号环绕的语句序列
属性 - (C++11 起) 任意数量的属性,应用到形参
类型说明符序列 - 形参声明的一部分,与在函数形参列表中相同
声明符 - 形参声明的一部分,与在函数形参列表中相同
抽象声明符 - 无名形参声明的一部分,与在函数形参列表中相同
1) 声明一个具名形参的 catch 子句
try { /* */ } catch (const std::exception& e) { /* */ }
2) 声明一个无名形参的 catch 子句
try { /* */ } catch (const std::exception&) { /* */ }
3) catch-all 处理块,可被任何异常激活
try { /* */ } catch (...) { /* */ }

解释

更多关于 throw 表达式的信息见抛出异常

try 块是一条语句,所以它能在任何语句能出现的地方出现(即作为复合语句中的语句之一,包含函数体复合语句)。有关围绕函数体的 try 块,见函数 try 块。下列描述同时适用于 try 块和函数 try 块

catch 子句的形参(类型说明符序列声明符,或者 类型说明符序列抽象声明符)决定何种类型的异常导致进入此 catch 子句。它不能是不完整类型抽象类类型右值引用类型 (C++11 起)或指向不完整类型的指针(但可以指向(可有 cv 限定的)void 的指针)。如果形参的类型是数组类型或函数类型,那么它会被处理成对应的指针类型(与函数声明类似)。

复合语句 中的任何语句抛出了异常时,以类型 E异常对象处理块序列 中的每个 catch 子句的形参类型 T,按 catch 子句的列出顺序进行匹配。在满足下列任一条件时异常就会匹配:

  • ET 是同一类型(忽略 T 上的顶层 cv 限定符)
  • T 是到(可有 cv 限定的)E 的左值引用
  • TE 的无歧义公开基类
  • T 是到 E 的无歧义公开基类的引用
  • T 是(可有 cv 限定的)U const U& (C++14 起),且 U 是指针或成员指针类型,且 E 也是能通过下列转换中的一个或多个转换成 U 的指针或成员指针类型
(C++17 起)
  • T 是指针或成员指针,或 const 指针的引用 (C++14 起),而 Estd::nullptr_t
(C++11 起)
try
{
    f();
}
catch (const std::overflow_error& e)
{} // 如果 f() 抛出 std::overflow_error 就会执行它(“相同类型”规则)
catch (const std::runtime_error& e)
{} // 如果 f() 抛出 std::underflow_error 就会执行它(“基类”规则)
catch (const std::exception& e)
{} // 如果 f() 抛出 std::logic_error 就会执行它(“基类”规则)
catch (...)
{} // 如果 f() 抛出 std::string 或 int 或任何其他无关类型就会执行它

全捕获(catch-all)子句 catch (...) 匹配任何类型的异常。如果存在,那么它必须是 处理块序列 中的最后一个 catch 子句。全捕获块可以用来确保不可能有异常从提供不抛出异常保证的函数中不被捕获而逃逸。

如果检测完所有 catch 子句后仍然没有匹配,那么就会如 throw 表达式中所述,到外围的 try 块继续异常的传播。如果没有剩下的外围 try 块,那么就会执行 std::terminate(此情况下,由实现定义是否完全进行栈回溯:抛出未捕获的异常可以导致程序终止而不调用任何析构函数)。

当进入一个 catch 子句时,如果它的形参是异常类型的基类,那么它会从异常对象的基类子对象进行复制初始化。否则它会从异常对象复制初始化(这个复制遵循复制消除规则)。

try
{
    std::string("abc").substr(10); // 抛出 std::length_error
}
// catch (std::exception e) // 从 std::exception 基类复制初始化
// {
//     std::cout << e.what(); // 丢失来自 length_error 的信息
// }
catch (const std::exception& e) // 多态对象基类的引用
{
    std::cout << e.what(); // 打印来自 length_error 的信息
}

如果 catch 子句的形参是引用类型,那么对它所做的任何更改都会反映到异常对象之中,且如果以 throw; 重抛这个异常,那么它可以被另一个处理块观测到。如果形参不是引用,那么任何对它的更改都是局域的,且它的生存期在处理块退出时结束。

在 catch 子句内,可以使用 std::current_exception 把异常捕获到一个 std::exception_ptr 之中,而且可以使用 std::throw_with_nested 来构建嵌套的异常。

(C++11 起)

gotoswitch 语句不能用来转移控制进入 try 块以及处理块。

除了抛出或重抛异常以外,普通的 try 块(非函数 try 块)之后的 catch 子句还可以通过 returncontinuebreakgoto,或通过抵达它的 复合语句 尾而退出。任何这些情况,都会销毁异常对象(除非存在指代它的 std::exception_ptr 实例)。

注解

不保证捕获指针的 catch 子句能够匹配 throw 表达式 throw NULL;,因为异常对象类型可以是整数类型,但可以确保任何指针或成员指针的 catch 子句能够匹配 throw nullptr;

如果派生类的 catch 子句位于基类的 catch 子句后,那么就永远不会执行派生类的 catch 子句:

try
{
    f();
}
catch (const std::exception& e)
{} // 在 f() 抛出 std::runtime_error 时执行
catch (const std::runtime_error& e)
{} // 死代码!

如果用 goto 退出 try 块,且这个 goto 所执行的任何块作用域自动变量的析构函数由于抛异常而退出,那么那些异常会被该变量在其中定义的 try 块捕获到:

label:
    try
    {
        T1 t1;
        try
        {
            T2 t2;
            if (condition)
                goto label; // 销毁 t2,然后销毁 t1,再跳到 label
        }
        catch (...) {} // 捕获来自 t2 析构函数的异常
    }
    catch (...) {}     // 捕获来自 t1 析构函数的异常

关键词

try, catch, throw

示例

以下代码演示 try-catch 块的几种用法:

#include <iostream>
#include <vector>
 
int main()
{
    try
    {
        std::cout << "抛出整数异常...\n";
        throw 42;
    }
    catch (int i)
    {
        std::cout << "  整数异常已捕获,它的值是:" << i << '\n';
    }
 
    try
    {
        std::cout << "创建一个大小为 5 的 vector...\n";
        std::vector<int> v(5);
        std::cout << "访问 vector 的第 11 个元素...\n";
        std::cout << v.at(10); // vector::at() 会抛出 std::out_of_range
    }
    catch (const std::exception& e) // 按基类的引用捕获
    {
        std::cout << "  标准异常已捕获,它的信息是:'" << e.what() << "'\n";
    }
 
}

可能的输出:

抛出整数异常...
  整数异常已捕获,它的值是:42
创建一个大小为 5 的 vector...
访问 vector 的第 11 个元素...
  标准异常已捕获,它的信息是:'out_of_range'

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 98 C++98 switch 语句可以转移控制进入 try 块以及处理块 已禁止
CWG 210 C++98 会以 throw 表达式来匹配 catch 子句 会以异常对象来匹配 catch 子句
CWG 1166 C++98 未指明匹配到异常类型是到抽象类的引用类型的 catch 子句时的行为 catch 子句不能对应抽象类类型
CWG 1769 C++98 当 catch 子句声明的异常类型是异常对象的基类时,
该 catch 子句的形参的初始化可能会用到转换构造函数
此时该形参会从异常对象的
对应基类子对象复制初始化
CWG 2093 C++98 具有成员指针类型的异常对象无法通过限定性转换匹配具有成员指针类型的处理块 可以匹配