16 March 2022

C++ 核心指南目录

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 可以表示多种信息:不同单位的数值。这样,我们就只能猜测这四个整数类型代表了什么。很可能,前两个是坐标 xy ,那么后两个是什么呢?

使用注释和参数名可以提供一些信息,但是我们可以在明确一些:

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 个以上的布尔量作为参数的接口
  • (困难)检查过多使用原生类型作为参数的函数