让我们说我认识一个不熟悉C ++的人。他没有绕过指针(正确地说是这样),但他拒绝通过引用传递。他总是使用传值。原因是他觉得“通过引用传递物体是一种破碎设计的标志”。
该程序是一个小型图形程序,大多数传递的问题是数学Vector(3元组)对象。有一些很大的控制器对象,但没有比这更复杂。
我发现很难找到反对仅使用堆栈的杀手论据。
我认为按值传递对于诸如向量之类的小对象来说是好的,但即使这样,代码中也会发生许多不必要的复制。按价值传递大型物品显然是浪费,很可能不是你想要的功能。
在专业方面,我相信堆栈在分配/解除分配内存方面更快,并且具有恒定的分配时间。
我能想到的唯一主要论点是堆栈可能会溢出,但我猜这是不可能发生的?是否有任何其他参数反对仅使用stack / pass by值而不是通过引用传递?
答案 0 :(得分:14)
Subtyping-polymorphism是一种传递值不起作用的情况,因为您将派生类切片到其基类。也许对某些人来说,使用子类型多态是一种糟糕的设计?
答案 1 :(得分:6)
如果不使用引用,就会有各种各样的事情 - 从复制构造函数开始。引用(或指针)是基本的,无论他喜欢与否,他都在使用引用。 (引用的一个优点或缺点是,通常,您不必更改代码来传递(const)引用。)并且没有理由不在大多数时间使用引用。
是的,对于没有动态分配要求的小型物体来说,通过价值是可以接受的,但是如果没有具体的测量说“没有参考”,所谓的开销是(a)可感知的(和)可感知的,那么徘徊自己仍然是愚蠢的。 b)重要。 “过早优化是所有邪恶的根源” 1 。
1 各种归因,包括C A Hoare(虽然显然他不赞成)。
答案 2 :(得分:6)
你朋友的问题与他的宗教信仰不同。给定任何函数,总是考虑通过值,引用,const引用,指针或智能指针传递的优缺点。然后决定。
我在这里看到的破碎设计的唯一标志是你朋友的盲目宗教。
也就是说,有一些签名没有带来太大的影响。按值取一个const可能很愚蠢,因为如果你保证不改变对象那么你也可以不自己制作它。当然,除非它是一个原语,在这种情况下,编译器可以足够聪明,仍然可以参考。或者,有时将指针指针作为参数是笨拙的。这增加了复杂性;相反,您可以通过引用指针来逃避它,并获得相同的效果。
但是不要把这些指导原则放在一边;总是考虑你的选择,因为没有正式的证据可以消除任何替代方案的用处。
答案 3 :(得分:5)
原因是他觉得“通过引用传递物体是设计破碎的标志”。
虽然纯粹出于技术原因这在C ++中是错误的,但总是使用pass-by-value对于初学者来说是一个足够好的近似 - 它肯定很多比通过指针传递所有东西更好(或者甚至比通过参考传递一切)。它会使一些代码效率低下,但是,嘿!只要这不打扰你的朋友,不要被这种做法不适当地打扰。只是提醒他有一天他可能想重新考虑。
另一方面,这个:
有一些大的控制器对象,但没有比这更复杂。
是个问题。你的朋友在谈论破碎的设计,然后所有代码使用的是一些3D矢量和大型控制结构? 那是一个破碎的设计。好的代码通过使用数据结构实现了模块化。似乎并非如此。
......一旦你使用这样的数据结构,没有传递引用的代码可能确实变得非常低效。
答案 4 :(得分:5)
我认为问题本身存在巨大的误解。
一方面堆栈或堆分配对象之间没有关系,另一方面传递值或引用或指针。
堆栈与堆分配
在可能的情况下总是更喜欢堆栈,然后为您管理对象的生命周期,这更容易处理。
但在某些情况下可能无法实现:
我可能会错过一些,但在这种情况下,您应该使用SBRM(Scope Bound Resources Management)来利用堆栈生命周期管理功能,例如使用智能指针。
传递:值,引用,指针
首先,语义:
有所不同请注意,默认情况下,某些语言(像Haskell这样的功能类型)不提供引用/指针。一旦创建,值就是不可变的。除了处理外部环境的一些解决方法之外,它们不受此使用的限制,并且它以某种方式使调试更容易。
你的朋友应该知道传递引用或传递指针绝对没有错误:例如swap
的东西,它不能用pass-by-value实现。
最后,多态性不允许按值传递语义。
现在,让我们谈谈表演。
通常很好地接受内置函数应该通过值传递(以避免间接),并且用户定义的 big 类应该通过引用/指针传递(以避免复制)。实际上, big 通常意味着复制构造函数不是一件容易的事。
然而,有一个关于小型用户定义类的未解决问题。最近发表的一些文章表明,在某些情况下,pass-by-value可能允许编译器进行更好的优化,例如,在这种情况下:
Object foo(Object d) { d.bar(); return d; }
int main(int argc, char* argv[])
{
Object o;
o = foo(o);
return 0;
}
在这里,智能编译器能够确定o
可以在没有任何复制的情况下进行修改! (我认为函数定义必须是可见的,我不知道Link-Time Optimization是否能解决这个问题)
因此,性能问题只有一种可能性,例如:衡量。
答案 5 :(得分:3)
我会说不使用C中的指针是新手程序员的标志。
听起来你的朋友害怕指点。
请记住,C ++指针实际上是从C语言继承而来的,而C是在计算机功能强大时开发的。尽管如此,速度和效率仍然至关重要,直到今天。
那么,为什么要使用指针呢?它们允许开发人员优化程序以更快地运行或者使用更少的内存,否则它将会使用它们!参考数据的存储位置比复制所有数据更有效。
对于那些开始编程的人来说,指针通常是一个难以理解的概念,因为所有的实验都涉及小阵列,可能是一些结构,但基本上它们包括使用几兆字节(如果你很幸运的话)当你在房子周围放置1GB内存时。在这个场景中,几个MB都不算什么,通常太少,不会对程序的性能产生重大影响。
所以让我们夸大一点。想想带有2147483648个元素的 char数组 - 2GB的数据 - 您需要传递给将磁盘写入所有数据的函数。现在,您认为哪种技术更有效/更快?
当你没有4GB内存时会发生什么?你是否会因为害怕使用指针而花费$并购买RAM芯片?
重新复制内存中的数据听起来有点多余,而且浪费了计算机资源。
无论如何,请耐心等待你的朋友。如果他想在生命的某个阶段成为一名认真/专业的程序员,他最终将不得不花时间真正理解指针。
祝你好运。
答案 6 :(得分:3)
首先,除了递归情况外,堆栈很少在本网站外溢出。
关于他的推理,我认为他可能是错的,因为他过于笼统,但他所做的可能是正确的......或不是?
例如,Windows窗体库使用具有4个成员的Rectangle
结构,Apple的QuartzCore也具有CGRect
结构,并且那些结构始终通过值传递。我想我们可以将它与Vector with 3浮点变量进行比较。
然而,由于我没有看到代码,我觉得我不应该判断他做了什么,尽管我觉得尽管他过于笼统,但他可能会做出正确的事情。
答案 7 :(得分:3)
我认为按值传递对于诸如向量之类的小对象来说是好的,但即使这样,代码中也会发生许多不必要的复制。按价值传递大型物品显然是浪费,很可能不是你想要的功能。
这并不像你想象的那么明显。 C ++编译器非常积极地执行复制省略,因此您可以经常传递值而不会产生复制操作的成本。在某些情况下,传递值might even be faster。
在出于性能原因谴责问题之前,您至少应该制定基准来支持它。而且他们可能很难创建,因为编译器通常会消除性能差异。
所以真正的问题应该是语义之一。您希望代码如何表现?有时,引用语义是你想要的,然后你应该通过引用传递。如果您特别想要/需要值语义,那么您可以传递值。
有一点赞成通过价值。它有助于实现更具功能性的代码风格,副作用更少,默认不变性。这使得许多代码更容易推理,并且它也可以使代码并行化更容易。
但事实上,两者都有自己的位置。并且从不使用pass-by-reference绝对是一个重要的警示标志。
在过去的6个月左右,我一直在尝试将pass-by-value作为默认值。如果我没有明确需要引用语义,那么我试着假设编译器会为我执行复制省略,所以我可以通过值传递而不会失去任何效率。
到目前为止,编译器并没有让我失望。我敢肯定,我会遇到一些情况,我必须回去改变一些通过引用传递的调用,但是当我知道
时我会这样做答案 8 :(得分:2)
如前所述,引用和指针之间的最大区别在于指针可以为null。如果一个类需要数据,则引用声明将使其成为必需。添加const将使其“只读”,如果这是调用者所希望的。
提到的传递值'缺陷'根本不是真的。按值传递所有内容将彻底改变应用程序的性能。原始类型(即int,double等)通过值传递时并没有那么糟糕,但是当通过值传递类实例时,会创建临时对象,这需要构造函数,然后在类和所有类上调用析构函数类中的成员变量。当使用大类层次结构时,这是令人恼火的,因为必须同时调用父类构造函数/析构函数。
另外,仅仅因为向量传递值并不意味着它只使用堆栈内存。 heap可以用于每个元素,因为它是在传递给方法/函数的临时向量中创建的。如果向量本身也可能必须通过堆重新分配,如果它达到其容量。
如果正在传递值,以便不修改调用者值,那么只需使用const引用。
答案 9 :(得分:2)
到目前为止,我所看到的答案都集中在性能上:传递引用比传值更快的情况。如果您专注于通过值传递不可能的案例,那么您的论证可能会更成功。
小元组或向量是一种非常简单的数据结构。更复杂的数据结构共享信息,并且共享不能直接表示为值。您需要使用引用/指针或模拟它们的东西,例如数组和索引。
很多问题归结为形成图形或定向图形的数据。在这两种情况下,您都需要在数据结构中存储边缘和节点的混合。现在您遇到的问题是,相同的数据需要位于多个位置。如果您避免引用,那么首先需要复制数据,然后需要在每个其他副本中仔细复制每个更改。
你朋友的论点可以归结为:解决任何复杂到足以被图表表示的问题都是一个糟糕的设计....
答案 10 :(得分:2)
我能想到的唯一主要论点 是堆栈可能 溢出,但我猜它是 不太可能发生这种情况?是 还有其他任何反对的论据 仅使用堆栈/传递值作为 反对以参考方式传递?
嗯,天啊,从哪里开始...
正如您所说,“代码中发生了许多不必要的复制”。假设您有一个循环,您可以在这些对象上调用函数。使用指针而不是复制对象可以加速执行一个或多个数量级。
您无法在堆栈上传递可变大小的数据结构,数组等。您必须动态分配它并传递指针或引用开头。如果你的朋友没有碰到这个,那么是的,他是“C ++新手。”
正如您所提到的,所讨论的程序很简单,并且大多使用非常小的对象,如图形3元组,如果元素是双精度,则每个24个字节。但在图形方面,处理4x4阵列很常见,它们处理旋转和平移。这些将是每个128字节,所以如果一个程序必须处理这些将是每次函数调用慢五倍由于复制增加的值传递。使用pass-by-reference,在32位可执行文件中传递3元组或4x4数组只会涉及复制一个4字节指针。
在富含寄存器的CPU架构上,如ARM,PowerPC,64位x86,680x0 - 但不是32位x86 - 指针(和引用,它们是穿着花哨的合成服装的秘密指针)通常会被传递或在寄存器中返回,与堆栈操作中涉及的内存访问相比,这真的非常快。
你提到堆栈空间不足的可能性。是的,那就是一个可能为课堂作业写的小程序。但几个月前,我正在调试商业代码,这可能是在main()下面的80个函数调用。如果他们使用pass-by-value而不是pass-by-reference,那么堆栈将是巨大的。并且为了避免你的朋友认为这是一个“破碎的设计”,这实际上是一个基于WebKit的浏览器,使用GTK +在Linux上实现,所有这些浏览器都是最先进的,并且函数调用深度对于专业代码来说是正常的
某些可执行的体系结构限制了单个堆栈帧的大小,因此即使您本身可能没有用完堆栈空间,也可能会超出这个范围并最终得到无法构建的完全有效的C ++代码。这样一个平台。
我可以继续下去。
如果您的朋友对图形感兴趣,他应该看一下图形中使用的一些常见API:Linux上的OpenGL和XWindows,Mac OS X上的Quartz,Windows上的Direct X.他应该看看大型C / C ++系统的内部结构,如WebKit或Gecko HTML渲染引擎,或任何Mozilla浏览器,或GTK +或Qt GUI工具包。它们都通过引用传递大于单个整数或浮点数的任何东西,并且通常通过引用而不是函数返回值来填充结果。
没有任何严肃的现实世界C / C ++印章 - 我的意思是 nobody - 按值传递数据结构。这是有原因的:它只是在效率低下且容易出错。
答案 11 :(得分:1)
他有一点意见。通过值传递的优点是子例程不能巧妙地修改它们的参数。传递非const 引用将表明每个函数都有丑陋的副作用,表明设计不佳。
简单地向他解释vector3 &
和vector3 const&
之间的区别,并演示后者如何通过vec_function( vector3(1,2,3) );
中的常量初始化,而不是前者。通过const引用是一个简单的值传递优化。
答案 12 :(得分:0)
给你的朋友买一本好的c ++书。通过引用传递非平凡对象是一种很好的做法,可以为您节省大量不必要的构造函数/析构函数调用。这也与分配免费商店和使用堆栈无关。您可以(或应该)通过引用传递在程序堆栈上分配的对象,而不使用任何免费存储。你也可以完全忽略免费商店,但这会让你回到你朋友可能没想到的旧堡垒日 - 否则他会为你的项目选择一个古老的f77编译器,不是吗......?