C++ 最佳实践:按值抛出,按const引用捕获

示例

通常,按值(而不是按指针)但由(常量)引用捕获可认为是一种好习惯。

try {
    // throw new std::runtime_error("Error!");   // 不要这样!
    // 这将创建一个异常对象
    // 在堆上,将需要您抓住
    //指针并自己管理内存。这个可以
    // 导致内存泄漏!
    
    throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
    std::cout << e.what() << std::endl;
}

引用捕获是一种好习惯的原因之一是,它消除了在传递到捕获块(或传播到其他捕获块)时重建对象的需求。通过引用捕获还可以对异常进行多态处理,并避免对象切片。但是,如果要抛出异常(例如throw e;,请参见下面的示例),则仍然可以获取对象切片,因为throw e;无论声明什么类型,该语句都会复制该异常:

#include <iostream>

struct BaseException {
    virtual const char* what() const { return "BaseException"; }
};

struct DerivedException : BaseException {
    // "virtual" keyword is optional here
    virtual const char* what() const { return "DerivedException"; }
};

int main(int argc, char** argv) {
    try {
        try {
            throw DerivedException();
        } catch (const BaseException& e) {
            std::cout << "第一个捕获块: " << e.what() << std::endl;
            // Output ==> 第一个捕获块: DerivedException

            throw e; // 这会将异常更改为BaseException
                     // 而不是原始的DerivedException!
        }
    } catch (const BaseException& e) {
        std::cout << "第二个捕获块: " << e.what() << std::endl;
        // Output ==> 第二个捕获块: BaseException
    }
    return 0;
}

如果您确定不做任何更改异常的操作(例如添加信息或修改消息),则按const引用进行捕获可以使编译器进行优化并提高性能。但这仍可能导致对象拼接(如上例所示)。

警告:当心不要在catch块中引发意外的异常,尤其是与分配额外的内存或资源有关的异常。例如,由于在复制异常字符串时内存耗尽,可能会构造logic_error,runtime_error或它们的子类抛出bad_alloc异常,在设置了各自的异常掩码的日志记录期间可能会抛出I / O流,等等。