找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 4188|回复: 5

【翻译】C++编程:传值还是传const&

[复制链接]
发表于 2021-5-1 01:55:05 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
原文:https://medium.com/@vgasparyan19 ... onst-c-f8944171e3ce
译者:0xAA55

C++编程:传值还是传const&

以下的代码是错的。试着找出里面的错误吧。

void foo(const std::shared_ptr<Widget>& widget);

经验法则

作为C++程序员,你大概记得在刚跳进这粪坑的时候,问过的这样的问题:

你:请问老师,string周围的这些是啥?为什么int可以直接是int,但string却必须是const string&

老师:吧啦吧啦吧啦,(在黑板上写写画画),吧啦吧啦吧啦。

你:哦我懂了(你当然懂了),老师,那既然这样的话我什么时候应该用哪个?

老师:这好办。所有这些内置的类型比如intboolchar应该直接传值。但其它类型比如结构体或者类,就需要用const xxx&的方式传递引用。哦对了,string它并不是一个内置类型,它是个类,而且你看,你的 Visual Studio 都把它显示成了别的颜色。

或者你并没有老师,你有的是个先辈,或者你干脆就是自己百度谷歌然后抄 stackoverflow 的那种类型的家伙。不论如何,你选择了一个非常简单的规则,这作为初学者而言,“大道从简”是一件好事,因为它可以让你知道如何快速上手,但有时候也会让你被问题卡住。

但这还没完,你会在学习中再次回到这个问题上来。因为你有机会读到Scott Meyers写的《高效开发C++:55种方式改善你的程序和设计》(Effective C++: 55 Specific Ways to Improve Your Programs and Designs)。其中就有:“第20则:传参应该多传引用少传值。”这里说的正好就是这个问题。你一边读一边发出“我懂了”的声音,一边看到这样的两行总结:

请记住:

  • 应该尽可能传引用而不是传值。它更加高效而且避免你的程序代码运行起来上下不搭边。
  • 但是这条法则并不适用于内置类型、STL遍历器、函数对象。对于这些东西,通常情况下传值更合理。

我打赌你读到“STL遍历器”之前就跳过了内容直接看下一条了。

并且我相信你发现了我们实际上讨论的是对于智能指针std::shared_ptr应当传值还是传const引用的问题。

对于机器而言是原型,对于你而言是接口

函数原型定义了函数的返回值和参数的个数与类型。你在看“声明”的时候看到的基本就是这些玩意儿。

std::istream& getline(std::istream& input_stream, std::string& str);

这个是简化了的众所周知的std::getline函数。这样的声明包含了对于编译器而言必要的信息。编译器需要知道输入参数的数量,它们的大小和顺序,从而生成机器指令来完成调用。与此同时,它还包含了对于人类,也就是这个函数的使用者而言的有价值的信息。人类靠直觉判断出str是一个输出参数,用来接收函数的返回结果,也就是这个函数读出来的“行”。当然如果它是const std::string& str,那就完全不是这回事儿了。但实际上加上去的const修饰并不会让编译器产生的机器码和不加const修饰的时候那个函数相关的机器码有什么不同。事实上,你的编译器在生成机器码的时候并不管函数参数有没有const修饰。“Const正确性”对于C++而言是一个很美好的概念。它定义接口的人和使用接口的人之间的契约。

总之就是你定义你的接口,让你的用户知道你的接口是干嘛的,你是怎么设计它的。

回到代码

当我们说代码是错的,并不是说代码有Bug。要么代码性能不行,要么代码“有方言”。对于“代码方言”这一点,它在技术上是好东西。它让你知道虽然代码能运行(因此不是Bug),但是会警告你代码有深层次的问题。这些“代码方言”的说法因语言而异,因行业而异。

为了理解代码错在了哪,让我们先快速回顾一下std::shared_ptr是什么。它是一个智能指针,用 引用计数 来包装一个指针,并且提供了像指针的接口。在任何时候,引用计数都可以指示出有多少shared_ptr共同拥有 这个资源。在每次增加一个拥有者的时候,引用计数的数值增加,而在拥有者被销毁(生命周期外,或者被析构)的时候引用计数会减少。最终,在引用计数减到零的时候,原始指针被delete,资源被释放。

在考虑到这一点的情况下,我们再看一遍函数,响亮地想出来!

void foo(... widget);

我们知道类型里肯定某处会写个Widget,但它具体是如何的呢?其实是要看情况的。我会问我自己以下三个问题:

  • 我是否需要在函数里修改原对象?
  • 复制一个Widget是否昂贵?
  • 我是否需要负责管理对象的生命周期?

这些问题有8(2³)个可能的答案组合,但是由于它们之间互相关联,因此其中一些组合是无关的。

Q1 Q2 Q3 参数
Yes - Yes std::shared_ptr<Widget> widget
Yes - No Widget& widget
No Yes Yes std::shared_ptr<const Widget> widget
No Yes No const Widget& widget
No No - Widget widget

在继续之前,我建议做个实验:从右往左反过来看这张表,先看参数,再尝试回答上面列出的问题。想象一下,你阅读的代码里正好有这么一个函数,它使用了这样的参数,思考一下这样使用参数的意图。

因此,不仅特定的情况决定了接口的设计,而且接口的设计也解释了特定的情况。考虑到这一点,让我们最终回到我们从开头的时候讨论的代码。

为什么我们的表种没有const std::shared_ptr<Widget>&的情况?是我们忘了试验它嘛?让我们通过反向思维的方式来找出答案:如果有人设计了这样的接口,他的理由是什么。

void foo(const std::shared_ptr<Widget>& widget);

我会如此解读:

  • 我需要一个指向Widget的指针,但不是一般的指针,我要一个智能的,具体来说就是共享的指针。
  • 但它并不是一个shared_ptr<const Widget>,因为我会修改原对象,所以请注意。
  • 还记得我说过要共享指针吗?我其实并不想复制一个共享指针并成为一个共有者。基本上,我并不会去使用它的共享的部分相关的功能,但如果这个对象没有被封装进共享指针,则它应该被封装进共享指针。如果是这样,通常就会造成 双重释放异常

这不仅没有道理,而且它的方言听起来还像个合法代码。它带来了不确定性和潜在的新错误。

结论

当你经常编写C++,你就会经常看到const&,以至于你习惯了它、不去质疑它的正确性。绝大多数情况下使用const&是对的。让shared_ptr如此特别的是其独特的理念。但这并不是C++语言和STL中唯一的理念。对它们应当采取新的观点,比如去思考std::remove的算法为何要erase配套(Erase–remove惯用法

多观察“第20则:传参应该多传引用少传值。”提到的“这条法则并不适用于内置类型、STL遍历器、函数对象”。其实可能有更多的地方不应该适用这条规则呢。总之,多读书可以显著改变生活。

马丁·福勒(Martin Fowler)曾经有句著名的话:“任何傻瓜都可以编写计算机可以理解的代码。然而只有好的程序员才能编写人类可以理解的代码。”代码除了指示机器如何工作以外,它还有除此以外的职能。我们需要设计出能展现用途、理念的,清晰而又富有表现力的界面。对于正确合理的计算机开发非常重要。


回复

使用道具 举报

 楼主| 发表于 2021-5-10 18:03:54 | 显示全部楼层
watermelon 发表于 2021-5-10 08:51
Fortran里面为了保证效率与内存空间,只能传地址(笑哭)

C++是传错了就会造成性能浪费和逻辑错误,并不是单纯的规则。
回复 赞! 1 靠! 0

使用道具 举报

发表于 2021-5-5 16:32:52 | 显示全部楼层
通常来说,需要修改变量的值就【传引用】,否则【传值】。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2021-5-5 18:14:26 | 显示全部楼层
美俪女神 发表于 2021-5-5 16:32
通常来说,需要修改变量的值就【传引用】,否则【传值】。


大错特错。请阅读原文。
回复 赞! 靠!

使用道具 举报

发表于 2021-5-10 08:51:33 | 显示全部楼层
Fortran里面为了保证效率与内存空间,只能传地址(笑哭)
回复 赞! 靠!

使用道具 举报

发表于 2021-5-11 11:49:47 | 显示全部楼层
0xAA55 发表于 2021-5-10 18:03
C++是传错了就会造成性能浪费和逻辑错误,并不是单纯的规则。

的确是这样的,可以可以。
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-7-27 12:49 , Processed in 0.039782 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表