通过 const 引用按值捕获的最佳实践

一般来说,按值(而不是通过指针)抛出是一种好的做法,但是通过(const)引用来捕获。

try {
    // throw new std::runtime_error("Error!");   // Don't do this!
    // This creates an exception object
    // on the heap and would require you to catch the
    // pointer and manage the memory yourself. This can
    // cause memory leaks!
    
    throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
    std::cout << e.what() << std::endl;
}

通过引用捕获是一个好的做法的一个原因是它消除了在传递给 catch 块时(或当传播到其他 catch 块时)重构对象的需要。通过引用捕获还允许以多态方式处理异常并避免对象切片。但是,如果要重新抛出异常(如 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 << "First catch block: " << e.what() << std::endl;
            // Output ==> First catch block: DerivedException

            throw e; // This changes the exception to BaseException
                     // instead of the original DerivedException!
        }
    } catch (const BaseException& e) {
        std::cout << "Second catch block: " << e.what() << std::endl;
        // Output ==> Second catch block: BaseException
    }
    return 0;
}

如果你确定不打算更改异常(例如添加信息或修改消息),那么使用 const 引用可以使编译器进行优化并提高性能。但这仍然会导致对象拼接(如上例所示)。

警告: 小心在 catch 块中抛出意外异常,尤其是与分配额外内存或资源有关。例如,构造 logic_errorruntime_error 或它们的子类可能会因复制异常字符串时内存耗尽而丢失 bad_alloc,I / O 流可能会在日志记录期间抛出相应的异常掩码集等。