实施1:
foo(const Bar x);
实施2:
foo(const Bar & x);
如果在函数中不更改对象,为什么要复制它(实现1)。
编译器会自动优化吗?
摘要:即使对象在函数声明中声明为const
,仍然可以通过其他别名&
编辑对象。
如果您是编写库的人并且知道您的功能没有这样做,或者该对象足够大以证明每次操作的解除引用成本,那么
foo(const Bar & x);
是要走的路。
第2部分:
编译器会自动优化吗?
由于我们确定它们并不总是等价的,并且等价的条件非常重要,编译器通常很难确保它们,所以几乎可以肯定 no
答案 0 :(得分:5)
实际上,在这种情况下,您通常会使用方法2.
通常情况下,如果对象很小,您只会使用方法1,因此复制它一次比通过引用重复访问它更便宜(这也会产生成本)。在TC++PL中,Stroustrup开发了一个复数类,并按正确的值传递它。
答案 1 :(得分:4)
“如果在函数中不更改对象,为什么要复制它(实现1)。”
有一些奇怪的情况,通过引用传递的对象可能会被其他代码更改,例如
namespace g { int x = 666; }
void bar( int ) { g::x = 0; }
int foo( int const& a ) { assert( a != 0 ); bar( a ); return 1000/a; } // Oops
int main() { foo( g::x ); }
自从20世纪90年代中期以来,这种情况从未发生过。
因此,别名是该类型的单个参数的理论问题。
有两个相同类型的参数,它更有可能。例如,赋值运算符可能会传递它被调用的对象。当参数按值传递时(如交换习语的最小形式),这没有问题,但如果没有,则通常需要避免自我赋值。
你进一步问,
“这会由编译器自动优化吗?”
不,一般情况下,由于上述原因
编译器通常不能保证参数参数不会有别名(但是,一个例外是调用内联机器代码的地方)
然而,第三方面,语言可能可能已经支持编译器,例如,通过为程序员提供一种明确接受任何此类优化的方法,比如说“通过引用传递替换pass by value来优化此代码是安全的,请继续,编译器”
答案 2 :(得分:2)
在某些情况下可能会进行优化,但有很多事情可以阻止它。如果出现以下情况,编译器无法避免复制:
x
的地址或对其的引用,并将其传递给某些可能与原始地址进行比较的代码。foo
运行时对象可能会发生变化,例如,因为foo
会调用某些其他函数来更改它。我不确定这是否是你的意思是通过说“该对象不会在函数中被”来排除,但如果没有那么它就在游戏中。如果这些事情对您的计划有任何影响,您可以复制它:
如果您认为副本效率更高,您也会复制它,这通常被认为是int
等“小”类型的情况。标准算法中的迭代器和谓词也是按值进行的。
最后,如果您的代码计划无论如何都要复制对象(包括通过分配给现有对象),那么合理的习惯用法就是首先将副本作为参数。然后从参数中移动/交换。
答案 3 :(得分:1)
如果对象从其他地方更改怎么办?
void f(const SomeType& s);
void g(const SomeType s);
int main() {
SomeType s;
std::thread([&](){ /* s is non-const here, and we can modify it */}
// we get a const reference to the object which we see as const,
// but others might not. So they can modify it.
f(s);
// we get a const *copy* of the object,
// so what anyone else might do to the original doesn't matter
g(s);
}
如果对象是const,但有可变成员怎么办?然后你仍然可以修改对象,因此,无论你有原件的副本还是引用都非常重要。
如果对象包含指向另一个对象的指针怎么办?如果s
是const,则指针将是const,但指向的内容不受s
的常量影响。但是创建一个副本会(希望)给我们一个深层副本,所以我们得到了我们自己的(const)对象,其中一个单独的(const)指针指向一个单独的(非const)对象。
在许多情况下,const副本与const引用不同。