仅在C++中使用堆栈的后果

Bef*_*rem 17 c++ pass-by-reference pass-by-name

让我们说我认识一个不熟悉C++的人.他没有绕过指针(正确地说是这样),但他拒绝通过引用传递.他总是使用传值.原因是他觉得"通过引用传递物体是一种破碎设计的标志".

该程序是一个小型图形程序,大多数传递的问题是数学Vector(3元组)对象.有一些大的控制器对象,但没有比这复杂.

我发现很难找到一个反对只使用堆栈的杀手论点.

我认为按值传递对于诸如向量之类的小对象是好的,但即使这样,代码中也会发生许多不必要的复制.按值传递大型对象显然是浪费,很可能不是您想要的功能.

在专业方面,我相信堆栈在分配/解除分配内存方面更快,并且具有恒定的分配时间.

我能想到的唯一主要论点是堆栈可能会溢出,但我猜这是不可能发生的?是否有任何其他参数反对仅使用堆栈/传递值而不是通过引用传递?

ste*_*anv 14

Subtyping-polymorphism是一种情况,其中传递值不起作用,因为您将派生类切片到其基类.也许对某些人来说,使用subtyping-polymorphism是不好的设计?

  • @Jon Purdy:模板的使用也是一种多态 - 它被称为参数多态.我认为@Stefaanv在他的帖子中指的是多态性是Subtyping Polymorphism,我觉得他应该相应地改写他的答案. (3认同)
  • @Stefaanv:C++是一种多范式语言(并且支持两种主要类型的多态),使用正确的术语变得非常重要. (2认同)

wil*_*ell 7

你朋友的问题与他的宗教信仰不同.给定任何函数,总是考虑通过值,引用,const引用,指针或智能指针传递的优缺点.然后决定.

我在这里看到的破碎设计的唯一标志是你朋友的盲目宗教.

也就是说,有一些签名没有带来太大的影响.按值取一个const可能很愚蠢,因为如果你保证不改变对象那么你也可以不自己制作它.当然,除非它是一个原语,在这种情况下,编译器可以足够聪明,仍然可以参考.或者,有时将指针指针作为参数是笨拙的.这增加了复杂性; 相反,您可以通过引用指针来逃避它,并获得相同的效果.

但是不要把这些指导原则放在一边; 总是考虑你的选择,因为没有正式的证据可以消除任何替代方案的用处.

  1. 如果您需要根据自己的需要更改参数,但又不想影响客户端,请按值获取参数.
  2. 如果您想向客户端提供服务,并且客户端与服务没有密切关系,那么请考虑通过引用参数.
  3. 如果客户端与服务密切相关,则考虑不参数但编写成员函数.
  4. 如果您希望为与服务密切相关但彼此非常不同的客户系列编写服务功能,那么请考虑使用参考参数,并且可能使该功能成为需要此友谊的客户的朋友.
  5. 如果您根本不需要更改客户端,请考虑使用const-reference.

  • 我并不是说在参数值上指定const是愚蠢的.我只是认为,如果您从未打算更改参数,有时复制参数很愚蠢.当然,也许仅仅要求复制有一些必要的效果,也许还有其他情况这是有道理的.我所说的只是思考它. (2认同)

Jon*_*ler 6

如果不使用引用,就会有各种各样的事情 - 从复制构造函数开始.引用(或指针)是基础,无论他喜欢与否,他都在使用引用.(引用的一个优点或缺点是,通常,您不必更改代码来传递(const)引用.)并且没有理由不在大多数时间使用引用.

是的,对于没有动态分配要求的小型物体来说,通过价值是可以的,但是如果没有具体的测量说"没有参考",所谓的开销是(a)可察觉的和(b)重要的,那么自己就是愚蠢的. ."过早优化是万恶之源" 1.

1 各种归因,包括CA Hoare(虽然显然他不赞成).


Kon*_*lph 5

原因是他觉得"通过引用传递物体是一种破碎设计的标志".

虽然纯粹出于技术原因这在C++中是错误的,但总是使用pass-by-value对于初学者来说是一个足够好的近似 - 它肯定比通过指针传递所有内容好得多(或者甚至比通过引用传递所有内容更好).它会使一些代码效率低下,但是,嘿!只要这不打扰你的朋友,不要被这种做法不适当地打扰.只是提醒他,总有一天他可能想重新考虑一下.

另一方面,这个:

有一些大的控制器对象,但没有比这复杂.

个问题.您的朋友在谈论破裂的设计,然后把所有的代码使用几个3D矢量和大型控制结构?是一个破碎的设计.好的代码通过使用数据结构实现了模块化.似乎并非如此.

...一旦你使用这样的数据结构,没有按引用传递的代码可能确实变得非常低效.


Mat*_* M. 5

我认为问题本身存在巨大的误解.

一方面堆栈或堆分配的对象之间没有关系,另一方面传递值或引用或指针.

堆栈与堆分配

在可能的情况下总是更喜欢堆栈,然后为您管理对象的生命周期,这更容易处理.

但在某些情况下可能无法实现:

  • 虚拟建筑(想想工厂)
  • 共享所有权(尽管你应该总是试图避免它)

我可能会错过一些,但在这种情况下,您应该使用SBRM(Scope Bound Resources Management)来利用堆栈生存期管理功能,例如使用智能指针.

通过:值,引用,指针

首先,语义有所不同:

  • value,const reference:方法不会修改传递的对象
  • reference:传递的对象可能被方法修改
  • 指针/ const指针:与引用相同(对于行为),但可能为null

请注意,默认情况下,某些语言(像Haskell这样的功能类型)不提供引用/指针.一旦创建,值就是不可变的.除了处理外部环境的一些解决方法之外,它们不受此使用的限制,并且它以某种方式使调试更容易.

你的朋友应该知道传递引用或传递指针绝对没有问题:例如swap,它不能用pass-by-value实现.

最后,多态性不允许按值传递语义.

现在,让我们谈谈表演.

它通常是得到普遍认同的内置插件应该按值传递(以避免间接)和用户定义的课,应参照/指针传递(避免复制).实际上通常意味着拷贝构造函数是不平凡的.

然而,有一个关于小型用户定义类的开放性问题.最近发表的一些文章表明,在某些情况下,按值传递可能允许从编译器进行更好的优化,例如,在这种情况下:

Object foo(Object d) { d.bar(); return d; }

int main(int argc, char* argv[])
{
  Object o;
  o = foo(o);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

在这里,智能编译器能够确定o可以在没有任何复制的情况下进行修改!(我认为函数定义必须是可见的,我不知道Link-Time Optimization是否能解决这个问题)

因此,性能问题只有一种可能性,例如:测量.