C++中catch必须声明类型,不可写catch();正确写法如catch(std::exception& e),省略变量名也需写类型,否则编译报错。

catch 里必须写类型,不能只写 catch()
很多刚学 C++ 的人会误以为 catch() 是万能兜底,结果编译直接报错:error: expected type-specifier before ')' token。C++ 标准强制要求每个 catch 必须声明一个带类型的参数(哪怕你不用它),哪怕是省略变量名,也得写类型。
常见写法对比:
try {
throw std::runtime_error("boom");
} catch (std::exception& e) { // ✅ 正确:引用捕获,避免拷贝
std::cout << e.what() << "\n";
} catch (...) { // ✅ 正确:C++ 唯一允许的无类型 catch,但只能放最后
std::cout << "unknown exception\n";
}
错误写法(编译不过):
catch () { ... } // ❌ 编译失败
catch (auto e) { ... } // ❌ C++17 也不支持 auto 在 catch 参数中(除非用模板 catch,但那是高级用法)
catch (std::exception e) { // ⚠️ 能编译,但会触发异常对象拷贝,可能抛异常(不推荐)
throw 表达式后面不能跟 void 或空语句
throw 是表达式,不是语句;它必须有操作数(即抛出的对象),且该对象类型需可复制或可移动。常见踩坑点:
立即学习“C++免费学习笔记(深入)”;
- 写成
throw;(重抛)是合法的,但**只能出现在已处于异常处理流程中的catch块内**;在try块或普通函数里单独写throw;会导致未定义行为(通常 terminate) -
throw void();、throw nullptr;、throw 0;都可能编译通过,但语义混乱——除非你明确设计了对应类型的catch,否则几乎一定漏捕获 - 抛出局部对象时注意生命周期:
throw std::string("hello");安全(临时对象会被复制/移动),但throw local_str;(其中local_str是栈上std::string)也安全,因为 throw 会调用拷贝/移动构造
noexcept 函数里 throw 会直接 terminate
如果函数声明了 noexcept(包括隐式 noexcept(true),比如析构函数、移动操作符),而内部却执行了 throw,程序不会进入 catch,而是立即调用 std::terminate() —— 连 catch(...) 都拦不住。
典型场景:
- 自定义析构函数里调用了可能抛异常的函数(如
close()失败返回 -1,但你误 throw) - 移动构造函数标记为
noexcept(推荐做法),但内部调用了可能 throw 的容器操作 - 使用
std::vector::push_back()等标准库函数时没注意其是否noexcept(C++11 起多数基础操作已加)
检查方式很简单:看函数声明末尾有没有 noexcept 或 noexcept(true)。不确定时,宁可不加 noexcept,也不要冒险。
不要在 catch 里“吞掉”异常却不记录
写 catch(...) { } 或 catch(std::exception&) { return; } 是高危操作,尤其在线程、回调、资源管理上下文中。看似“防止崩溃”,实则掩盖逻辑错误、阻断调试路径、导致资源泄漏(比如本该在异常路径中释放的锁或内存)。
合理做法:
- 捕获后至少打日志:
catch (const std::exception& e) { log_error("failed: ", e.what()); throw; }(重抛) - 转换异常类型再抛:
catch (const std::system_error& e) { throw MyIoError(e.code(), e.what()); } - 仅在明确知道该异常可安全忽略时才空 catch,例如某些平台特定的信号包装异常,且有充分测试覆盖
最常被忽略的一点:析构函数里禁止 throw(C++11 起默认 noexcept),所以即使你想在 catch 中清理,也不能靠抛异常来通知失败 —— 得改用返回码、设置标志位或 abort。











