
用 extern "C" 包裹 C 头文件声明,否则链接失败
C++ 编译器会对函数名做 name mangling,而 C 链接器只认原始符号名。不加 extern "C",哪怕头文件里函数声明完全一致,ld 也会报 undefined reference to 'xxx'。
- 在 C++ 源文件中包含 C 头文件时,必须用
extern "C"包裹:extern "C" { #include "c_library.h" } - 如果 C 头文件本身没加保护(比如没写
#ifdef __cplusplus),就绝不能直接#include,必须手动包一层 - 不要只包声明、漏掉 typedef 或宏定义——只要 C 头里有链接相关的内容(函数、全局变量),就得整个包住
Wrapper 类的成员函数别直接暴露 C 指针,用 RAII 封装资源生命周期
裸指针传进传出 Wrapper,等于把内存管理责任甩回上层,极易引发 double-free 或 use-after-free。
- 构造函数调用 C 的创建函数(如
c_create()),析构函数调用对应销毁函数(如c_destroy()) - 把 C 的 handle 类型(比如
void<em></em>或struct c_ctx)存为私有成员,不提供 getter 返回原始指针 - 移动语义要显式实现:移动构造/赋值后,原对象的 C 指针必须置为
nullptr,避免析构时重复释放 - 拷贝应默认禁用(
= delete),除非 C 库明确支持引用计数或深拷贝
错误码转换别硬写 if-else 链,用 constexpr 查表映射
C 接口通常返回整数错误码(如 -1, EINVAL, ENOMEM),C++ 层若每次手动判断,既啰嗦又容易漏分支。
- 定义一个
constexpr std::array或constexpr std::span,把 C 错误码映射到std::error_code或自定义枚举 - 在 Wrapper 方法末尾统一调用转换函数,例如:
auto wrap_foo() -> std::expected<void, std::error_code> { int ret = c_foo(); if (ret != 0) return make_error_code(c_to_cpp_error(ret)); return {}; } - 注意:C 错误码可能为正(如某些嵌入式库)、也可能为负(如 POSIX),查表前先确认符号约定
- 别在转换逻辑里 throw 异常——异常路径会干扰 C 库内部状态(比如它可能已 malloc 但未 free)
const 正确性要穿透到底层,C 函数声明里的 const 不能丢
C 头文件里如果参数是 const uint8_t*,Wrapper 的对应参数也必须是 const,否则编译器可能静默接受非常量实参,破坏接口契约。
立即学习“C语言免费学习笔记(深入)”;
- 所有输入缓冲区、配置结构体指针,只要 C 声明带
const,Wrapper 接口就必须保持 - 输出参数(如
int* out_val)不能加const;但若 C 库实际不修改,且你控制底层实现,可考虑用std::optional<t></t>替代输出指针,更安全 - 对于只读的 C 结构体(如
const struct c_config*),Wrapper 可封装为不可变 view 类型,禁止任何 setter
C++ 封装 C 接口最麻烦的从来不是语法,而是两边对“谁负责释放”“谁允许修改”“错误发生后状态是否可继续用”的隐含假设不一致。每加一层 wrapper,都要多问一句:这个 const、这个指针、这个返回值,C 库真这么承诺了吗?










