该自己写重试而非用现成库的场景包括:嵌入式环境运行C++11、需精确控制退避策略(如100ms/200ms/400ms)、重试逻辑须与业务状态机耦合(如三次失败后切换备用通道)。

什么时候该自己写重试,而不是用现成库
多数场景下,libcurl、grpc 或 boost::asio 自带的重试机制够用;但如果你在嵌入式环境跑 C++11、或需要精确控制退避策略(比如失败后等 100ms,再等 200ms,再等 400ms),又或者重试逻辑要和业务状态机耦合(比如“重试三次失败就切换备用通道”),那就得手写——库往往太重或不可控。
用 std::function + 循环实现基础重试框架
核心是把任务包装成可调用对象,配合简单循环和延迟。不依赖第三方,C++11 起就能跑:
bool retry_until_success(std::function<bool()> task, int max_attempts = 3, int base_delay_ms = 100) {
for (int i = 0; i < max_attempts; ++i) {
if (task()) return true;
if (i < max_attempts - 1) {
std::this_thread::sleep_for(std::chrono::milliseconds(base_delay_ms * (1 << i)));
}
}
return false;
}常见错误现象:std::this_thread::sleep_for 在某些实时系统上可能被信号中断,导致实际休眠时间远小于预期;若任务本身有副作用(如发包、写日志),重复执行前需确认幂等性。
base_delay_ms * (1 是指数退避,避免雪崩式重试;线性退避用 <code>base_delay_ms * (i + 1)- 若任务返回
std::optional<T>或std::expected<T, E>(C++23),需改判空值或错误码,不能只看bool - 别在循环里捕获
std::exception后吞掉——至少记录what(),否则失败时完全没线索
如何让重试感知错误类型并选择性放弃
不是所有错误都值得重试:网络超时可以重试,404 或 401 就该立刻停。这时需把判断逻辑外移:
立即学习“C++免费学习笔记(深入)”;
bool should_retry(const std::exception& e) {
const char* msg = e.what();
return std::string_view(msg).find("timeout") != std::string_view::npos ||
std::string_view(msg).find("Connection refused") != std::string_view::npos;
}使用场景:HTTP 客户端封装、串口通信、MQTT 连接建立。关键点在于——
- 不要硬编码字符串匹配,生产环境建议用枚举错误码或继承自
std::runtime_error的自定义异常类 -
std::system_category().default_error_condition可用于标准化系统错误(如EAGAIN、ETIMEDOUT),比字符串更可靠 - 若任务抛出异常,
retry_until_success必须用try/catch包裹,否则直接崩溃
避免重试逻辑污染业务代码的两种写法
直接在业务函数里塞 for 循环,很快会变成面条代码。推荐以下两种轻量解法:
方案一:用 RAII 封装重试上下文(适合短生命周期任务)
struct RetryContext {
int attempts = 0;
const int max_attempts;
RetryContext(int max) : max_attempts(max) {}
bool should_continue() { return ++attempts <= max_attempts; }
};方案二:模板化,支持任意签名(适合通用工具层)
template<typename F, typename... Args>
auto retry_with_backoff(F&& f, Args&&... args) -> decltype(f(args...)) {
constexpr int max = 3;
for (int i = 0; i < max; ++i) {
try {
return f(std::forward<Args>(args)...);
} catch (const std::exception& e) {
if (i == max - 1 || !should_retry(e)) throw;
std::this_thread::sleep_for(std::chrono::milliseconds(100 * (1 << i)));
}
}
}容易踩的坑:模板推导可能失败,特别是当 f 返回 void 时,decltype(f(...)) 会报错;此时需特化 void 版本,或改用 std::invoke_result_t + if constexpr 分支。
最常被忽略的是时钟精度——std::chrono::milliseconds(100) 在 Windows 上实际分辨率可能是 15ms,Linux 下也未必精确;对毫秒级敏感的场景,得用 std::chrono::high_resolution_clock 校准休眠起点,而非依赖 sleep_for 的理论值。


















