为什么会使用func( const Class &value )
而不只是func( Class value )
?当然,现代编译器将使用任一语法执行最有效的操作。这仍然是必要的,还是仅仅是非优化编译器时代的延续?
显然,事实并非如此。很久以前我从一些代码中得到了gcc这样做的印象,但实验证明这是错误的。信用归功于Michael Burr,如果在这里给出,他将对similar question的答案进行提名。
答案 0 :(得分:17)
2个签名之间存在2个大的语义差异。
第一种是在类型名称中使用&
。这表示该值通过引用传递。删除它会导致对象通过值传递,这将基本上将对象的副本传递给函数(通过复制构造函数)。对于只需要读取数据的操作(典型的const &
),执行对象的完整副本会产生不必要的开销。对于不小或者是集合的类,这种开销并不是微不足道的。
第二个是使用const
。这可以防止函数通过value
引用意外修改值的内容。它允许调用者采取一些措施来保证函数不会突变该值。是的,在许多情况下,传递副本可以让调用者更加深入地了解这一情况。
答案 1 :(得分:8)
第一个表单不会创建对象的副本,它只是将引用(指针)传递给现有副本。第二种形式创建一个副本,这可能是昂贵的。这不是优化的东西:拥有一个对象的副本与原始文件之间存在语义差异,而复制需要调用该类的复制构造函数。
对于没有复制构造函数的非常小的类(如<16字节),使用值语法而不是传递引用可能更有效。这就是您看到void foo(double bar)
而非void foo(const double &var)
的原因。但是为了不优化微观优化代码,一般来说,您应该通过引用传递所有实际对象,并且只传递int
和void *
等内置类型。值。
答案 2 :(得分:8)
有一个巨大的差异,没有人提到过:对象切片。在某些情况下,您可能需要 const&
(或&
)才能获得正确的行为。
考虑另一个继承自Derived
的班级Class
。在客户端代码中,您创建了一个Derived
的实例,并将其传递给func()
。如果您有func(const Class&)
,则会传递相同的实例。正如其他人所说,func(Class)
将制作副本,您将在Class
中拥有Derived
(不是func
)的新(临时)实例}。
如果func
依次进行向下转换,则行为差异(非性能)可能很重要。比较运行以下代码的结果:
#include <typeinfo.h>
struct Class
{
virtual void Foo() {};
};
class Derived : public Class {};
void f(const Class& value)
{
printf("f()\n");
try
{
const Derived& d = dynamic_cast<const Derived&>(value);
printf("dynamic_cast<>\n");
}
catch (std::bad_cast)
{
fprintf(stderr, "bad_cast\n");
}
}
void g(Class value)
{
printf("g()\n");
try
{
const Derived& d = dynamic_cast<const Derived&>(value);
printf("dynamic_cast<>\n");
}
catch (std::bad_cast)
{
fprintf(stderr, "bad_cast\n");
}
}
int _tmain(int argc, _TCHAR* argv[])
{
Derived d;
f(d);
g(d);
return 0;
}
答案 3 :(得分:4)
当然现代编译器会做的 使用其中任何一个最有效 语法
编译器不编译你的“意思”,它编译你告诉它的内容。编译器只适用于较低级别的优化和程序员忽略的问题(例如for循环中的计算,死代码等)。
你告诉编译器在第二个例子中要做的是制作一个类的副本 - 它将不经思考地做 - 即使你没有使用它,这就是你要求编译器做的事情。 / p>
第二个示例明确要求编译器使用相同的变量 - 节省空间和宝贵的周期(不需要复制)。 const是存在错误的 - 因为Class &value
可以被写入(有时是需要的)。
答案 4 :(得分:3)
以下是一些参数声明之间的区别:
copied out modifiable func(Class value) Y N Y func(const Class value) Y N N func(Class &value) N Y Y func(const Class &value) N N N
其中:
因此func(Class value)
和func(const Class &value)
之间的差异是:
value
value
答案 5 :(得分:1)
如果您使用前者,然后尝试更改value
,则编译器会给您一个错误。
如果您使用后者,然后尝试更改value
,则不会。
因此,前者可以更容易地发现错误。
答案 6 :(得分:1)
第一个例子是通过引用传递。 C ++不是传递类型,而是传递对象的引用(通常,引用是用指针实现的......所以它可能是一个大小为4字节的对象)......在第二个例子中,对象按值传递。 ..如果它是一个大而复杂的对象,那么它可能是一个相当重量级的操作,因为它涉及到一个新的“类”的复制构造。
答案 7 :(得分:1)
优化编译器无法为您处理此问题的原因是单独编译的问题。在C ++中,当编译器为调用者生成代码时,它可能无法访问函数本身的代码。我所知道的最常见的调用约定通常让调用者调用copy-constructor,这意味着如果没有必要,编译函数本身就不可能阻止复制构造函数。
答案 8 :(得分:0)
最好是按值传递参数的唯一时间是你要复制参数。
std::string toUpper( const std::string &value ) {
std::string retVal(value);
transform(retVal.begin(), retVal.end(), charToUpper());
return retVal;
}
或者
std::string toUpper( std::string value ) {
transform(value.begin(), value.end(), charToUpper());
return value;
}
在这种情况下,如果value参数是常规对象,则第二个示例与第一个示例的速度相同,但如果value参数是R值,则速度更快。
虽然大多数编译器都会进行这种优化,但我不希望在C ++ 0X之前依赖此功能,因为我预计它可能会让大多数可能会改变它的程序员感到困惑。
请参阅Want Speed? Pass by Value.以获得比我能给出的更好的解释。