应将语义相近的原始参数封装为独立类型并加范围检查,单参数构造函数需加explicit,操作符返回值加const,接口命名与行为须符合最小惊讶原则,工厂函数应返回智能指针。

看参数类型是否裸露原始整数或字符串
如果一个构造函数或函数接收 int、std::string 这类通用类型,且多个参数语义相近(比如 Date(int, int, int)),就极可能被误用。编译器无法区分哪个是月、哪个是日——输错顺序或越界值(如 Date(13, 1, 2025))都能过编译,但运行时才暴露问题。
实操建议:
- 把每个有明确语义的参数封装成独立类型,例如
struct Month { explicit Month(int); int val; }; - 所有构造函数参数改用这些包装类型,强制调用方写成
Date(Month(3), Day(15), Year(2025)) - 在包装类型的构造函数里做范围检查(如
Month只接受 1–12),把错误拦在构造时而非使用时
查有没有隐式转换路径
单参数构造函数若没加 explicit,就等于给用户开了后门:他们能用字面量直接隐式构造对象,比如 process(42) 自动变成 process(Buffer(42)),而你本意是只接受已明确构造的 Buffer 实例。
实操建议:
- 对所有单参数构造函数加
explicit,除非你明确需要隐式转换(极少见) - 用
static_assert配合std::is_convertible_v检查是否意外支持了隐式转换 - 对重载操作符的返回值加
const,防止(a + b) = c这类非法赋值通过编译
验接口行为是否符合最小惊讶原则
如果 operator+ 实际做了资源释放,或 size() 返回的是字节数而非元素个数,用户按常识推测的行为就会出错。这类误用不会导致编译失败,但会引发逻辑 bug,更难排查。
实操建议:
- 命名必须直白:
open_file()就该打开文件,不能顺带做权限校验并抛异常 - 与内置类型对齐:自定义容器的
size()应返回size_t,empty()应返回bool,不另起炉灶 - 避免“必须记得做某事”的设计,比如要求用户手动调用
cleanup();改用 RAII 或返回std::unique_ptr自动管理
测工厂函数是否推卸资源责任
返回裸指针的工厂函数(如 Widget* create_widget();)本质是在说:“你自己管释放”。90% 的调用方会漏掉 delete,或重复 delete,这是典型的“易误用”接口。
实操建议:
- 工厂函数直接返回
std::unique_ptr<widget></widget>或std::shared_ptr<widget></widget> - 若需定制删除逻辑(如跨 DLL 场景),用
std::shared_ptr的自定义 deleter,而不是让用户传入void(*)(void*) - 禁止返回引用或指针指向栈对象——这种接口连正确使用都困难,更别说防误用了
真正难的不是加一层包装类型,而是坚持在每个新增接口上问一句:这个参数,用户有没有可能传错?如果错了,是编译期报错,还是运行期崩溃,还是静默错? 编译期拦截不了的,至少得让错误表现得足够明显。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











