CppCoreGuidelines I.4 精确强化接口的类型
I.4: Make interfaces precisely and strongly typed
理由
类型乃文档之最简最佳者。其定义明确,可读性好,又支持运行时检测。另外,类型精确的代码,编译器和执行系统可以更好的进行优化。
例子
void pass(void* data); // 弱且无类型的 void* 很可疑
调用者不确定能传入了什么类型的数据,因为没有标记为 const
也不知道能不能修改数据值。因为所有的指针类型都能默默地转成void*
,对调用者来说,倒是很容易提供这类数据。
然而,被调用的函数,却需要用static_cast
转化成没有验证过的类型使用。这样又容易出错,又繁琐。
在进行设计C++程序时,只有当无法描述数据类型的时候,小范围内使用
const void*
。考虑下,能否改用 variant
或指向基类的指针。
替代方案
模板参数往往能避免void*
,就可以用T*
或 T&
替代。对于泛型代码来说,
T
可以是通用的类型,或是模板参数设定的类型。
例子
draw_rect(100, 200, 100, 500); // 这些数字指的是什么? draw_rect(p.x, p.y, 10, 20); // 10 和 20 的单位是啥?
清楚的是:调用者用这四个参数描述一个长方形。但是不清楚这些参数代表长方形的哪部分。另外,整数类型 int
可以表示多种信息:不同单位的数值。这样,我们就只能猜测这四个整数类型代表了什么。很可能,前两个是坐标 x
和 y
,那么后两个是什么呢?
使用注释和参数名可以提供一些信息,但是我们可以在明确一些:
void draw_rectangle(Point top_left, Point bottom_right); void draw_rectangle(Point top_left, Size height_width); draw_rectangle(p, Point{10, 20}); // 两个点 draw_rectangle(p, Size{10, 20}); // 一个角,一对边长
显然,我们不可能通过静态类型系统避免所有错误。比如上面例子,第一个参数,如果调用者传进其他位置,而不是左上角的点。可能需要注释和变量名来澄清。
例子
set_settings(true, false, 42); // 这个数指的是什么?
从参数类型和参数值看不出来这个函数是要设置什么东西。
按照以下改法,看起来就比较明确、安全,且可读性更好:
alarm_settings s{}; s.enabled = true; s.displayMode = alarm_settings::mode::spinning_light; s.frequency = alarm_settings::every_10_seconds; set_settings(s);
如果是一组相关的开关量,可以考虑标记枚举:
enable_lamp_options(lamp_option::on | lamp_option::animate_state_transitions);
例子
以下例子,从接口看不出time_to_blink
是秒还是毫秒?
void blink_led(int time_to_blink) // 不好 -- 单位不清 { // do something with time_to_blink } void use() { blink_led(2); }
例子
std::chrono::duration
类型可以帮助明确时间长度的单位。
void blink_led(milliseconds time_to_blink) // 好 -- 时间单位明确 { // do something with time_to_blink } void use() { blink_led(1500ms); }
这个函数也可以改写成可以接受任何时间单位:
#include <iostream> #include <chrono> using namespace std; using namespace chrono; template<class rep, class period> void blink_led(duration<rep, period> time_to_blink) // 好 -- 单位明确 { // assuming that millisecond is the smallest relevant unit auto milliseconds_to_blink = duration_cast<milliseconds>(time_to_blink); // ... cout << "blink " << milliseconds_to_blink.count() << " ms" << endl; } int main() { blink_led(2s); blink_led(150ms); return 0; }
blink 2000 ms blink 150 ms
强化
- (简单)统计多少使用
void*
作为参数和返回类型 - (简单)统计超过 1 个以上的布尔量作为参数的接口
- (困难)检查过多使用原生类型作为参数的函数