为什么写func(const Class& value)更好?

时间:2009-11-02 01:56:58

标签: c++ coding-style

为什么会使用func( const Class &value )而不只是func( Class value )?当然,现代编译器将使用任一语法执行最有效的操作。这仍然是必要的,还是仅仅是非优化编译器时代的延续?

  • 只需添加,gcc将为任一语法生成类似的汇编代码输出。也许其他编译器没有?

显然,事实并非如此。很久以前我从一些代码中得到了gcc这样做的印象,但实验证明这是错误的。信用归功于Michael Burr,如果在这里给出,他将对similar question的答案进行提名。

9 个答案:

答案 0 :(得分:17)

2个签名之间存在2个大的语义差异。

第一种是在类型名称中使用&。这表示该值通过引用传递。删除它会导致对象通过值传递,这将基本上将对象的副本传递给函数(通过复制构造函数)。对于只需要读取数据的操作(典型的const &),执行对象的完整副本会产生不必要的开销。对于不小或者是集合的类,这种开销并不是微不足道的。

第二个是使用const。这可以防止函数通过value引用意外修改值的内容。它允许调用者采取一些措施来保证函数不会突变该值。是的,在许多情况下,传递副本可以让调用者更加深入地了解这一情况。

答案 1 :(得分:8)

第一个表单不会创建对象的副本,它只是将引用(指针)传递给现有副本。第二种形式创建一个副本,这可能是昂贵的。这不是优化的东西:拥有一个对象的副本与原始文件之间存在语义差异,而复制需要调用该类的复制构造函数。

对于没有复制构造函数的非常小的类(如<16字节),使用值语法而不是传递引用可能更有效。这就是您看到void foo(double bar)而非void foo(const double &var)的原因。但是为了不优化微观优化代码,一般来说,您应该通过引用传递所有实际对象,并且只传递intvoid *等内置类型。值。

答案 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

其中:

  • 复制:调用函数时输入参数的副本
  • out:value是一个“out”参数,这意味着func()内的修改在返回后会在函数外部显示
  • 可修改:可以在func()
  • 中修改值

因此func(Class value)func(const Class &value)之间的差异是:

  • 第一个复制输入参数(通过调用类复制构造函数),并允许func()内的代码修改value
  • 第二个制作副本,并且允许func()内的代码修改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.以获得比我能给出的更好的解释。