传递(int x)和(const int &x)之间的区别

Difference between passing (int x) and (const int &x)

本文关键字:int 区别 之间 传递 const      更新时间:2023-10-16

我正在阅读《c++入门+(第6版)》这本书,我遇到了一些让我感到困惑的东西,所以请原谅我,我试图解释…

如果我有一个函数,它的原型是这样的:

void cube(double x);

和另一个函数的原型是这样的:

void cube(const double &x);

两者的区别是什么?对于第一个函数原型,值是按值传递的,这意味着它将被复制,因此不会被函数改变。对于第二个原型,值是按引用传递的,但它是一个常量引用,因此c++将创建一个匿名临时变量,并将参数的值赋给临时变量,从而模仿按值传递。所以,本质上这两个函数原型没有区别,对吧?那么(const double &x)的意义是什么呢?

对于第二个原型,值是通过引用传递的,但它是一个常量引用,因此 c++将创建一个匿名临时变量,并将参数的值赋给临时变量,从而模仿值传递。

尽管c++在某些情况下会这样做。例如,如果传递一个返回double的表达式,c++将创建一个临时的:

double v = 123.456;
cube(5*v+321.0123);

然而,它不一定会这样做。例如,如果您调用

double v = 123.456;
cube(v);

c++将对v的引用直接传递给cube()函数。在这种情况下,对v的并发修改对于正在运行的cube()函数是"可见的"。

所以,本质上这两个函数原型并没有什么区别,对吧?

是的,这两者之间没有太大的区别。假设doubledouble的引用占用相同的空间量,那么性能也不会有差异。

那么(const double &x)的意义是什么?

虽然通过值传递double类型和通过常量引用传递double类型没有什么区别,但是在处理其他类型(如std::string)时可能会有很大的区别。当你将算法编码为模板时,通过常量引用获取形参变得非常有用,例如,当你必须写

void cube(const T& v);

T可以是任何类型。恒定引用方法允许您控制正在进行的复制的数量,因为从某个对象大小开始,传递引用比传递副本要便宜得多。

对于以下两个原型

void cube(double x);

void cube(const double &x);

,从调用者和被调用者的角度来看,行为将是相同的,因为两者都不允许将修改传播到调用者。然而,在后一种(const引用)示例中,x将通过引用而不是通过值传递。如果double足够大,或者它是一个更大的数据类型(例如,struct),通过引用传递将避免复制大量数据(因为只传递指针)。

二级性能影响(例如,对缓存的影响与数据副本的权衡)都非常依赖于实现,不仅取决于处理器/缓存体系结构和编译器,还取决于程序的结构和运行时用例。

对于基本类型,使用const&不会获得任何好处。const&通常用于大对象,以避免可能昂贵和不必要的复制。

如果类型不支持复制,你还需要const&,如果你想禁止修改被引用的对象。

随着c++ 11的到来,移动语义质疑了"按值传递小对象,按常量引用传递大对象"的传统智慧据我所知,c++专家社区在这个话题上还没有达成新的共识。例如,看看"想要速度"有多正确?传递value"。

根据[dcl.init.ref] #5.1中的规则,标准明确要求将引用形参绑定到实参,而不是将引用绑定到某个副本(仅举个例子)。差异是可检测的,因此没有"as if"规则适用(在所有情况下)。

double a;
void foo(const double &x) { assert(&a == &x); }
void bar() { foo(a); }

也有一个实际的区别。在const double &x的情况下,x的名字只是一些双精度的别名,可以通过其他左值访问,这对优化有很多影响(如寄存器保存/恢复,指令重新排序等),例如:

  double a;
  void foo(const double &x) {
       ... = x;
       a = bar(); // here the compiler must assume that the assignment
                  // may modify x, the two statements can't be reordered
  }
  void foo(double x) {
       ... = x;
       a = bar(); // here the compiler knows that the assignment
                  // cannot modify x, and the two statements can be
                  // reordered
  }

嗯,有一些区别:

  • 使用void cube(double x),您在函数中制作变量x的本地副本,并且可以更改其值而不更改原始x的值。因此,在本地函数void cube中,您可以与x交互,就像与顺序变量一样。在void cube(const double& x)中,你只是传递一个指针(在c++中,引用是一个指针,但使用稍微不同的语法),const在这里意味着你不能改变这个地址中变量的值(指针所指向的)。因此,在局部函数void cube中,您应该像与常量变量一样与x交互。

性能差异如何?

对于double,没有性能上的区别,因为double是64位,而对double的引用是32位或64位,没有太大区别。但是,假设你有一个struct:

struct some_very_big_struct {
    ...
}

,其中sizeof(some_very_big_struct)2^10字节,所以复制这个结构体需要花费很多时间和额外的内存,所以在这种情况下,传递一个引用是最好的选择。

虽然读/写操作会减慢速度,但在32位机器上,引用/指针只有32位,而双精度对象是64位。这在32位的字长下不能很好地播放。

这没有意义,真的。一个优化的编译器可能会将它们视为相同的,并生成相同的代码。

编辑:为了解释我自己,我应该强调"可能"。

严格的标准遵从性目前是判断编译器是否确实是c++编译器的一个坏方法——编译器经常滥用/忽略标准,而倾向于优化和语法糖。

至于(性能)差异,我想说的是测量传递双精度对象(单词大小)给函数的成本与从指针/引用(单词大小)获取的成本——在大多数情况下,它可以忽略不计。你传递的值越大,从指针/引用中获取的速度就会越快。
Casey Muratori提到了这一点,并鼓励传递稍微大于单词大小的结构。