Wiki
Exception Handling
Existing English translations were reused. 1 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.Exception Handling
1.1.Exception Handling Overview
In C++, various errors may occur during program execution, for example:
- File open failed
- Failed to open serial port
- Invalid parameter
- Array index out of bounds
- Memory allocation failed
- Configuration file format error
If every function determined errors solely by return values, the code would become relatively complex.
C++ provides an exception handling mechanism to separate "normal logic" from "error handling logic".
Exception handling mainly uses three keywords:
trycatchthrow
Function:
try: Place code that may throw an exceptionthrow:Throw an exceptioncatch:Catch and handle the exception
Syntax:
try
{
// 可能出现异常的代码
}
catch (异常类型 变量名)
{
// 异常处理代码
}
Example:
#include <iostream>
using namespace std;
int divide(int a, int b)
{
if (b == 0)
{
throw "除数不能为0";
}
return a / b;
}
int main()
{
try
{
int ret = divide(10, 0);
cout << "ret = " << ret << endl;
}
catch (const char* msg)
{
cout << "捕获到异常:" << msg << endl;
}
return 0;
}
Operation result:
捕获到异常:除数不能为0
After the program runs,divide(10, 0) an exception will be thrown, and the subsequent normal return will not continue to execute; the program will jump to catch to handle the exception.
1.2.throw throws an exception
throw Used to actively throw an exception.
Syntax:
throw 异常对象;
An exception object can be an ordinary type or a standard library exception object.
Example:
#include <iostream>
using namespace std;
void checkAge(int age)
{
if (age < 0)
{
throw "年龄不能小于0";
}
cout << "年龄为:" << age << endl;
}
int main()
{
try
{
checkAge(-10);
}
catch (const char* msg)
{
cout << "错误信息:" << msg << endl;
}
return 0;
}
Operation result:
错误信息:年龄不能小于0
When age < 0, the function throws an exception via throw and the program jumps to catch for execution.
1.3.catch Catch exception
catch Used to catch exceptions.
If the type of the thrown exception matches the type in catch, the corresponding catch code block will be entered.
Example:
#include <iostream>
using namespace std;
void test()
{
throw 10;
}
int main()
{
try
{
test();
}
catch (int e)
{
cout << "捕获到 int 类型异常:" << e << endl;
}
return 0;
}
Operation result:
捕获到 int 类型异常:10
throw 10; throws a int type exception, so it will be caught by catch (int e).
1.4.Catching different types of exceptions with multiple catch blocks
A try can be followed by multiple catch to handle different types of exceptions.
Example:
#include <iostream>
using namespace std;
void test(int type)
{
if (type == 1)
{
throw 10;
}
else if (type == 2)
{
throw 3.14;
}
else if (type == 3)
{
throw "字符串异常";
}
}
int main()
{
try
{
test(3);
}
catch (int e)
{
cout << "捕获到 int 异常:" << e << endl;
}
catch (double e)
{
cout << "捕获到 double 异常:" << e << endl;
}
catch (const char* e)
{
cout << "捕获到字符串异常:" << e << endl;
}
return 0;
}
Operation result:
捕获到字符串异常:字符串异常
Depending on the type thrown by throw , different catch code blocks are entered.
1.5.Catch all exceptions
If you do not care about the specific type of exception, you can use catch (...) to catch all exceptions.
Syntax:
catch (...)
{
// 捕获所有异常
}
Example:
#include <iostream>
using namespace std;
void test()
{
throw 3.14;
}
int main()
{
try
{
test();
}
catch (...)
{
cout << "捕获到未知类型异常" << endl;
}
return 0;
}
Operation result:
捕获到未知类型异常
Regardless of the type of exception thrown,catch (...) can be caught.
Note:
catch (...) Generally placed at the end.
try
{
test();
}
catch (int e)
{
cout << "int异常" << endl;
}
catch (...)
{
cout << "其他异常" << endl;
}
1.6.standard exception classes
In actual C++ engineering, it is not recommended to throw strings arbitrarily; more often, exception classes provided by the standard library are used.
Common exception classes:
std::exceptionstd::runtime_errorstd::logic_errorstd::invalid_argumentstd::out_of_rangestd::bad_alloc
Using standard exception classes requires including the header file:
#include <stdexcept>
1.6.1.runtime_error
runtime_errorIndicates runtime errors, such as file open failure, serial port open failure, network connection failure, etc.
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
void openSerialPort(const string& device)
{
if (device.empty())
{
throw runtime_error("串口设备名不能为空");
}
cout << "打开串口:" << device << endl;
}
int main()
{
try
{
openSerialPort("");
}
catch (const runtime_error& e)
{
cout << "运行时错误:" << e.what() << endl;
}
return 0;
}
Operation result:
运行时错误:串口设备名不能为空
e.what() The error information in the exception can be obtained.
1.6.2.invalid_argument
invalid_argument Indicates an invalid parameter.
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
void setSpeed(double speed)
{
if (speed < 0)
{
throw invalid_argument("速度不能为负数");
}
cout << "设置速度:" << speed << endl;
}
int main()
{
try
{
setSpeed(-1.5);
}
catch (const invalid_argument& e)
{
cout << "参数错误:" << e.what() << endl;
}
return 0;
}
Operation result:
参数错误:速度不能为负数
When the passed-in speed is less than 0, an invalid argument exception is thrown.
1.6.3.out_of_range
out_of_range indicates an out-of-bounds access.
vector of at() throws an exception when out of bounds.
Example:
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
int main()
{
vector<int> nums = {10, 20, 30};
try
{
cout << nums.at(5) << endl;
}
catch (const out_of_range& e)
{
cout << "访问越界:" << e.what() << endl;
}
return 0;
}
Operation result:
访问越界:vector::_M_range_check: __n (which is 5) >= this->size() (which is 3)
nums.at(5) is out of vector range, so it throws a out_of_range exception.
Note:
nums[5]; // 越界时不一定抛异常,可能直接产生未定义行为
nums.at(5); // 越界时会抛出 out_of_range 异常
1.7.catch std::exception
Many standard exception classes inherit from std::exception.
Therefore, in practical engineering, the following notation is often used:
catch (const std::exception& e)
{
cout << e.what() << endl;
}
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
void test()
{
throw runtime_error("程序运行出现错误");
}
int main()
{
try
{
test();
}
catch (const exception& e)
{
cout << "捕获到标准异常:" << e.what() << endl;
}
return 0;
}
Operation result:
捕获到标准异常:程序运行出现错误
runtime_error belongs to the standard exception type and can be caught by const exception&.
1.8.catch capture order
If a parent class exception and a subclass exception are caught at the same time, the subclass should be caught first, and then the parent class.
Incorrect usage:
try
{
throw runtime_error("运行时错误");
}
catch (const exception& e)
{
cout << "exception:" << e.what() << endl;
}
catch (const runtime_error& e)
{
cout << "runtime_error:" << e.what() << endl;
}
In the above pattern, runtime_error will be captured first by exception and the subsequent runtime_error branch becomes meaningless.
Recommended notation:
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
try
{
throw runtime_error("运行时错误");
}
catch (const runtime_error& e)
{
cout << "runtime_error:" << e.what() << endl;
}
catch (const exception& e)
{
cout << "exception:" << e.what() << endl;
}
return 0;
}
Operation result:
runtime_error:运行时错误
Summary:
Write subclass exceptions first, and parent class exceptions after.
1.9.Exceptions propagate between functions
Exceptions can propagate across functions.
If the current function does not handle the exception, the exception will continue to be passed to the calling upper-level function.
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
void func3()
{
throw runtime_error("func3 中出现错误");
}
void func2()
{
func3();
}
void func1()
{
func2();
}
int main()
{
try
{
func1();
}
catch (const exception& e)
{
cout << "main 中捕获异常:" << e.what() << endl;
}
return 0;
}
Operation result:
main 中捕获异常:func3 中出现错误
func3() After the exception is thrown,func2() and func1() are not handled, and the exception is finally propagated to main() and caught.
1.10.Rethrow exception
Sometimes, after the current function catches an exception, it only performs part of the handling and then continues to pass the exception to the upper layer for handling.
Can be used:
throw;
Rethrow the current exception.
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
void readConfig()
{
try
{
throw runtime_error("配置文件读取失败");
}
catch (const exception& e)
{
cout << "readConfig 中记录错误:" << e.what() << endl;
// 重新抛出异常
throw;
}
}
int main()
{
try
{
readConfig();
}
catch (const exception& e)
{
cout << "main 中最终处理:" << e.what() << endl;
}
return 0;
}
Operation result:
readConfig 中记录错误:配置文件读取失败
main 中最终处理:配置文件读取失败
The exception is first caught once in readConfig(), then rethrown via throw;, and finally caught again in main().
1.11.Custom exception class
Standard exception types are already sufficient to solve most problems.
If you wish to distinguish more specific errors, you can also define custom exception classes.
Custom exception classes typically inherit from std::runtime_error.
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
class SerialException : public runtime_error
{
public:
SerialException(const string& msg)
: runtime_error(msg)
{
}
};
void openSerial(const string& device)
{
if (device.empty())
{
throw SerialException("串口设备名为空");
}
cout << "打开串口:" << device << endl;
}
int main()
{
try
{
openSerial("");
}
catch (const SerialException& e)
{
cout << "串口异常:" << e.what() << endl;
}
catch (const exception& e)
{
cout << "其他异常:" << e.what() << endl;
}
return 0;
}
Operation result:
串口异常:串口设备名为空
When the serial port device name is empty, a SerialException type exception is thrown.
Note:
In ordinary learning stages, a large number of custom exception classes are not needed; the standard exception classes are already sufficient.
1.12.Exceptions and Constructors
Constructors have no return value.
If object initialization fails, an exception can be thrown in the constructor.
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
class SerialPort
{
public:
SerialPort(const string& device)
{
if (device.empty())
{
throw runtime_error("串口初始化失败:设备名为空");
}
m_Device = device;
cout << "串口初始化成功:" << m_Device << endl;
}
private:
string m_Device;
};
int main()
{
try
{
SerialPort port("");
}
catch (const exception& e)
{
cout << "创建对象失败:" << e.what() << endl;
}
return 0;
}
Operation result:
创建对象失败:串口初始化失败:设备名为空
If the constructor throws an exception, object creation fails, and the program jumps to catch to handle the exception.
Application scenario:
For example:
- Failed to open file
- Failed to open serial port
- Failed to read configuration
- Failed to initialize network connection
These situations can be considered for throwing an exception in the constructor.
1.13.Exceptions and destructors
Generally, exceptions should not be thrown in destructors.
Because if an exception is thrown again when the object is destroyed, it may cause the program to terminate directly.
Error example:
class Test
{
public:
~Test()
{
// 不推荐在析构函数中抛异常
// throw runtime_error("析构失败");
}
};
Recommended practice:
If releasing resources fails in a destructor, typically only the error is logged and no exception is further thrown.
#include <iostream>
using namespace std;
class File
{
public:
~File()
{
// 析构函数中尽量不要 throw
cout << "释放文件资源" << endl;
}
};
Summary:
Do not throw exceptions arbitrarily in destructors.
1.14.Exceptions and RAII
RAII is a very important concept in modern C++.
The core idea of RAII is:
Resources are acquired during object construction and released during object destruction.
Common resources include:
- Heap Memory
- File
- Serial Port
- Network Connection
- Lock
- Handle
When an exception occurs, local objects that have already been successfully created will automatically have their destructors called.
1.14.1.The problem with ordinary pointers
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
void test()
{
int* p = new int[100];
throw runtime_error("中途出现异常");
delete[] p;
}
int main()
{
try
{
test();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
Operation result:
中途出现异常
throw After that,delete[] p; will not be executed, which may cause a memory leak.
1.14.2.Use vector to avoid resource leaks
Example:
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;
void test()
{
vector<int> nums(100);
throw runtime_error("中途出现异常");
cout << nums.size() << endl;
}
int main()
{
try
{
test();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
Operation result:
中途出现异常
Even if an exception occurs,vector the object is automatically destructed, and its internal memory is automatically released.
1.14.3.Use smart pointers to manage resources
#include <iostream>
#include <memory>
#include <stdexcept>
using namespace std;
void test()
{
unique_ptr<int[]> p(new int[100]);
throw runtime_error("中途出现异常");
}
int main()
{
try
{
test();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
Operation result:
中途出现异常
unique_ptr will automatically release the memory; no manual delete[] is required.
Summary:
Exception handling must be understood together with RAII.
In modern C++, preference should be given to:
stringvectorunique_ptrshared_ptrfstream- Construction and destruction of custom classes
to manage resources, reducing manual new/delete.
1.15.Exceptions and return value error codes
Exceptions are not suitable for use everywhere.
1.15.1.Situations Suitable for Using Exceptions
Exceptions are suitable for handling serious errors that "should not occur frequently".
For example:
- Configuration file does not exist
- Failed to open serial port
- Obvious parameter error
- Initialization failed
- The program cannot continue running
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
void initSystem(const string& configPath)
{
if (configPath.empty())
{
throw runtime_error("配置文件路径为空");
}
cout << "系统初始化成功" << endl;
}
int main()
{
try
{
initSystem("");
}
catch (const exception& e)
{
cout << "初始化失败:" << e.what() << endl;
return 1;
}
return 0;
}
Operation result:
初始化失败:配置文件路径为空
The program then exits with status code 1.
1.15.2.Cases suitable for using return values
For frequent, recoverable minor errors, return values can be used.
For example:
- Serial port occasionally fails to read
- Network occasionally times out
- A certain frame of sensor data is invalid
- Motor feedback occasionally drops packets
Example:
#include <iostream>
using namespace std;
bool readEncoder()
{
// 模拟读取失败
return false;
}
int main()
{
if (!readEncoder())
{
cout << "编码器读取失败,本次循环跳过" << endl;
return 0;
}
cout << "编码器读取成功" << endl;
return 0;
}
Operation result:
编码器读取失败,本次循环跳过
Summary:
- Fatal errors during initialization can be handled with exceptions.
- Common errors in the run loop should preferably be handled with return values or status codes.
1.16.ROS2 / Exception usage ideas in robotics projects
In ROS2 or robotics projects, exception handling should be used with caution.
1.16.1.Suitable locations for throwing exceptions
For example, during the node startup phase:
- Parameter read failed
- Configuration file does not exist
- Serial port cannot be opened
- CAN device cannot be opened
- Hardware initialization failed
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
class MotorDriver
{
public:
MotorDriver(const string& device)
{
if (device.empty())
{
throw runtime_error("电机驱动初始化失败:设备名为空");
}
cout << "电机驱动初始化成功:" << device << endl;
}
};
int main()
{
try
{
MotorDriver driver("");
}
catch (const exception& e)
{
cout << "程序启动失败:" << e.what() << endl;
return 1;
}
return 0;
}
Operation result:
程序启动失败:电机驱动初始化失败:设备名为空
The program then exits with status code 1.
1.16.2.Locations not suitable for frequently throwing exceptions
For example, in a control loop:
while (true)
{
// 不推荐每次通信失败都 throw
}
More recommended:
bool readMotorState()
{
// 读取失败返回 false
return false;
}
int main()
{
if (!readMotorState())
{
cout << "读取电机状态失败,跳过本次控制周期" << endl;
}
return 0;
}
Operation result:
读取电机状态失败,跳过本次控制周期
Summary:
In robot control, periodic code places greater emphasis on stability and real-time performance; do not let exceptions fly around everywhere in high-frequency control loops.
1.17.Introduction to noexcept
noexcept Indicates that a function promises not to throw exceptions.
Syntax:
void func() noexcept
{
}
Example:
#include <iostream>
using namespace std;
void test() noexcept
{
cout << "这个函数承诺不抛异常" << endl;
}
int main()
{
test();
return 0;
}
Operation result:
这个函数承诺不抛异常
Note:
If a noexcept function actually throws an exception, the program usually terminates directly.
void test() noexcept
{
// 不应该这样写
// throw runtime_error("错误");
}
In the ordinary learning phase, you only need to know:
noexceptIndicates that the function does not throw exceptions- It is often seen in destructors, move constructors, and low-level resource release functions
- At this stage, there is no need to study the underlying mechanism in depth
1.18.Exception Handling Considerations
1.18.1.Prefer to use reference capture
Recommended:
catch (const exception& e)
{
cout << e.what() << endl;
}
Not recommended:
catch (exception e)
{
cout << e.what() << endl;
}
Reason:
Capturing by value creates a copy and may also cause object slicing issues.
1.18.2.Do not abuse exceptions
异常适合处理异常情况,不适合替代普通的 if else。
Not recommended:
try
{
if (score < 60)
{
throw "不及格";
}
}
catch (...)
{
cout << "成绩不及格" << endl;
}
Write ordinary logic directly:
if (score < 60)
{
cout << "成绩不及格" << endl;
}
1.18.3.Do not casually throw exceptions in destructors
In destructors, resource release should be made as safe as possible.
1.18.4.Standard exceptions take precedence over string exceptions
Recommended:
throw runtime_error("文件打开失败");
Not recommended:
throw "文件打开失败";
1.18.5.Severe exceptions can be uniformly caught in the main function
Example:
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
try
{
throw runtime_error("程序出现严重错误");
}
catch (const exception& e)
{
cout << "fatal error:" << e.what() << endl;
return 1;
}
return 0;
}
Run result (the program exits with status code 1 at the end):
fatal error:程序出现严重错误
After a serious exception occurs in the program, handle it uniformly in the main function to avoid the program crashing directly without any error information.
1.19.Summary
C++ Core content of exception handling:
try: Attempt to execute code that may throw an errorthrow: Throw an exceptioncatch: Catch and handle exceptionsstd::exception: Standard exception base classstd::runtime_error: Common runtime exceptionse.what(): Obtain exception information- Exceptions can propagate across functions
- Constructors may throw exceptions
- Destructors should not throw exceptions casually
- Exception handling should be understood in conjunction with RAII
- Do not overuse exceptions
For ordinary C++ learning and ROS2 engineering development, the key points to master are:
- Able to write basic
try / catch / throw - Can use
std::runtime_error - Can use
catch (const std::exception& e)Unifiedly catch standard exceptions - Know that exceptions are suitable for initialization failures, not for high-frequency control loops
- Understand that RAII can prevent resource leaks caused by exceptions