Linkerist / blog

Linkerist's blog.
Apache License 2.0
18 stars 0 forks source link

《The historical Inputs_and_Outputs compulsive item(avoid non-const reference) changed》 #27

Open Linkerist opened 3 years ago

Linkerist commented 3 years ago

出参一定要是指针?

image

群里之前有同学问过这个问题。 当时的规范中,强制了这一条,因为彼时的规范该条目主要来自Google C++ Style Guide其中一个历史版本,比较旧了,当时google还未更新此项。

这个条目是影响面比较大的争议项。有不少人反馈,之前我写了个issue(https://git.code.oa.com/standards/cpp/issues/71 )提议修改。 经过和委员们的思考和互动讨论,这个条目已经如提议改进了。 可以看到,现在的规范(https://git.code.oa.com/standards/cpp#51%E6%8E%A8%E8%8D%90%E8%BE%93%E5%85%A5%E5%92%8C%E8%BE%93%E5%87%BA) 中, 这个条目已不再强制。

对我们的影响(仅有积极影响)是:存量代码中这一条目对应的CodeCC告警(Is this a non-const reference? If so, make const or use a pointer)不用改。

该记录归档于此,以后编程可以注意一下。


issue引文:

这个强制条目可能需要改进一下

image

图中这项是一个影响非常大的争议项,问题在于强制。

:) 可能条目比较多,大家没太注意这项。 这个条目的逻辑稍微有点牵强,说了不少引用的优点,但是转而说不让用引用,理由是为了可读性。

再论可读性

这个条目成立基于这样一个前提:“相比引用,指针具有更高可读性”。 但是,这个断言并不成立

所谓优势

猜想到的,可能体现可读性优势的,是这种场景:

callee:
void copy1(const std::string& a, std::string* b);
void copy2(const std::string& a, std::string& b);
caller:
copy1(foo, &bar);
copy2(foo, bar);

然而,不少情况调用处是指针变量,不是一个变量实例前加&求址,这个所谓的优势荡然无存。

caller:
ptr_bar = &bar;
copy1(foo, ptr_bar); // 直接传指针变量

输入的已经用const修饰了,没用const修饰的引用很显然就是输出参数,指针并没有比引用更显式地表达输出。如此,指针作为出参并没有可读性优势。

即使退一步,假设这样的写法是存在优势的,这样的场景也有很大的局限性

因为会直接'&'这样写的,只是caller,更确切地说,是初始caller,所以,在一个调用链中,只有在最初的caller中才能体现这个假设的优势。

语义需要明确,而不是基于隐喻

怎么表达一个参数,是类型系统;用一个参数具体做什么,是具体的逻辑,这是独立的两件事,一件事不该隐喻另一件事。

可能可读性最好不要通过取址符这一层隐喻来实现。若论可读性,自然语言的可读性最佳,它可以表现在:

  1. IN/OUT这样的自然语言增强修饰。(windows以前的用法。我们可能不用宏,更不会用这么短的宏)。
  2. 显式的关键字。(比如RUST的mut,表示mutable,C#的out,表示output)
  3. 变量名。(这里对应形参的取名,直接self-documenting为出参)
  4. 注释。(如果3仍未满足)

毕竟,最终caller还是必然得看callee原型的。:) 应该不会有caller不看原型,直接因为有个&,就认为是输出了吧。

引用比指针具有更高的可读性

我们的程序有两种reference,一种是const的,一种不是。

引用的语义比指针更加明确,在这种非此即彼的场景中更是如此。

Modern C++中开发者更愿意把pointer看做nullable reference,而不是把reference看成const-initialized pointer。 引用的本质是常量指针,用意就是作为变量的别名。这样用目标变量别名即为出参,语义上也更自然。

返回值的本质

函数返回值的本质诉求在于:有一个容器,能容纳需要的输出信息,caller可以access这个容器。 当你选用函数出参实现函数返回值时,这也便成了函数出参的诉求。

如果你使用指针,它将是一个不确定的值,具有不明确的语义。 如果我不希望它为null,那么我为什么总是需要假定它为null呢?这给开发者非常高的心智负担。

若旨在可读性,那么,应该直接用Modern C++的返回方式。

强制的影响大

可行性上,众多项目底层接口(包括基础库的接口)本就是用引用的,如果强制不能用,这些功能是必然实现不了的。 成本上,即使可行,修改成本也过高了。这是未来成本。

如果强制,可能经过大幅度修改满足规范了,但是未来的一天,修改了此项。如果要通过检查,可能又得改回来。这是潜在的沉没成本。

综上,未来成本、潜在的沉没成本很高。规范中这一条目改进之后,这些成本可以减少。

该条目的提出者,也已经改正了此条目

google minds think alike,谷歌已经改正了这一项

具体commit: https://github.com/google/styleguide/commit/7a7a2f510efe7d7fc5ea8fbed549ddb31fac8f3e

https://google.github.io/styleguide/cppguide.html#Inputs_and_Outputs