作为常量和参考传递 - 值得吗?

时间:2011-02-20 21:24:23

标签: c++

  

可能重复:
  How to pass objects to functions in C++?

在我的游戏中,我过度使用了数学向量和运算符重载。

class Vector
{
   float x, y;
};

基本上所有关于我的Vector类(排除方法)。

我不是C ++方面的专家,我已经看过并阅读过传递为const并通过引用传递。

那么,以下代码示例中的性能差异在哪里?

Float RandomCalculation( Vector a, Vector b )
{
    return a.x * b.x / b.x - a.x * RANDOM_CONSTANT;
}

// versus..

Float RandomCalculation( Vector& a, Vector& b )
{
    return a.x * b.x / b.x - a.x * RANDOM_CONSTANT;
}

// versus..

Float RandomCalculation( const Vector& a, const Vector& b )
{
    return a.x * b.x / b.x - a.x * RANDOM_CONSTANT;
}
  • 我应该使用哪三个?为什么?
  • 每个选项的编译器优化过程有哪些优势?

  • 我何时何地必须特别小心?

6 个答案:

答案 0 :(得分:116)

传递const引用是传递对象的首选方法,作为传值的智能替代方法。当您通过const引用时,您通过引用获取参数(避免制作它的任何副本),但不能对原始对象进行任何更改(就像您按值获取参数时会发生的那样) )。

如果你考虑这三个功能:

void VersionOne(Vector v);
void VersionTwo(Vector& v);
void VersionThree(const Vector& v);

所有这些之间都有微妙的区别。例如,第一个函数将在传入Vector时调用复制构造函数,以便它具有自己的Vector本地副本。如果您的复制构造函数需要一段时间才能运行或执行大量资源分配和释放,这可能会很慢,但您可以对参数进行任何更改,而不会有任何更改传播回调用方的风险。当参数被清理时,函数末尾也会有一个析构函数调用,如果这个代价太大,建议避免这种设置。也就是说,对于小物体来说,这可能是完全可以接受的。

此函数的第二个版本通过引用接受Vector,这意味着该函数可以对Vector进行任何更改,并且更改将传播回调用方。每当你看到一个函数接受非const引用的参数时,就像这个VersionTwo函数一样,你应该假设它将修改参数,因为如果它不会进行任何修改,它将由const引用。如果您需要对Vector进行更改,您很可能会通过引用获取值;例如,通过旋转它,缩放它等。这里涉及的一个权衡是Vector在传递给这个函数时不会被复制,因此你将避免调用复制构造函数和析构函数。这可能会对您的程序产生性能影响,但如果这是您的推理,您可能应该通过const引用。需要注意的一点是,跟随引用非常类似于跟随指针(事实上,大多数引用实现只是将它们视为自动解引用指针),因此每次访问时都可能会遇到小的性能损失。数据通过参考。但是,只有剖析可以告诉你这是否是一个主要问题,除非你有特别的理由认为它是错误的,否则我不会担心它。

此函数的最终版本采用Vector const引用,与常规引用一样,可避免任何复制。但是,在Vector引用const时,您无法对函数内的Vector进行任何更改,因此客户可以认为Vector不会改性。 (是的,从技术上讲,如果编写得不好或有mutable数据成员,它可能会被修改,但我们现在将忽略它。这是高层次的想法,这里很重要)。如果您希望能够在不复制函数的情况下检查函数中的值并且不进行变更,那么此选项将会很好。

传递引用和传递 - const - 引用之间还有一个区别,那就是函数对rvalues的行为。如果您有一个临时的Vector对象 - 要么通过编写Vector()显式创建它,要么像编写v1 + v2那样对它进行一些数学运算 - 那么就不能传递那个临时的Vector通过引用获取其参数的函数,因为引用只能绑定到左值。这个想法是,如果你有这样的功能:

void DoSomething(Vector& v) {
     v.x = 0.0f;
}

然后编写

是没有意义的
DoSomething(v1 + v2);

因为这会改变临时表达式的x字段。为防止这种情况,编译器将拒绝编译此代码。

但是,C ++会产生异常并允许您将rvalues传递给通过const引用获取其参数的函数,因为直观地说,您不应该通过const引用来修改对象。因此,这段代码完全合法:

void DoSomething(const Vector& v) {
    cout << v.x << endl;
}

DoSomething(v1 + v2);

所以,总结一下 -

  1. 传值和传递 - const - 引用意味着类似的事情 - 您希望能够查看该值而无法对其进行修改。
  2. 任何时候你可以使用pass-by-value,你可以使用pass-by - const - 引用而不影响程序的正确性。但是,参考的间接性与复制和破坏参数的成本之间存在性能折衷。
  3. 非传递 - const - 引用应用于表示“我想修改参数。”
  4. 您无法将rvalues传递给通过非const引用获取参数的函数。
  5. 希望这有帮助!

答案 1 :(得分:8)

是否要复制传递给函数的对象? 如果按值传递对象"Vector a""Vector b",则必须处理复制它们的开销。对于小型结构,开销可以忽略不计。

如果通过引用或指针传递对象,则不会产生此开销。但是,通过指针或引用传递可能允许修改对象:

对象名称前面的const关键字用于保证您的函数不会修改通过引用或指针传递给函数的对象。这不仅会告诉其他程序员您的函数可以安全使用,而且编译器也会严格执行它。在将const关键字用于此目的时,const reference关键字对性能没有影响。

如果您有大型对象,请按const pointer或{{1}}传递。 如果要修改传递给函数的对象,请使用引用或指针。 如果您的对象是一个小结构,您可以按值传递它。

答案 2 :(得分:5)

你会得到很多答案来解决这个或那个问题。事情的真相是你需要测试它。您的对象相当小,因此传递值可能比通过引用传递更快或更快。只有通过分析您的代码,您才能知道。

答案 3 :(得分:1)

如果要更改参数并让客户端观察这些更改,则只能通过引用传递非const。

如果您不想更改参数,请按引用传递给const或按值传递。

如果要更改参数但对客户端没有影响,请按值传递。

答案 4 :(得分:0)

现在可能情况发生了变化,但多年前我做了一些测试,有几个C ++编译器和不同的变体(例如成员或非成员运算符,operator+根据operator+=定义,通过值或ref等传递。

我检查了两个时间和生成的汇编程序代码。结果高度依赖于使用哪个编译器......对于另一个编译器而言,最好的结果不是另一个编译器。

另外,遗憾的是,当时我从未能够获得执行相同计算的手动展开的C代码的相同性能。关闭是......但C仍然有点快。

答案 5 :(得分:0)

简短的回答是,在实践中几乎没有什么区别。

现在回答很久:

第一版和第二版之间会有性能差异,可能是第二版和第三版之间的差异。

当您调用第一个版本(Vector a, Vector b)时,将在堆栈上创建每个参数的副本。这涉及分配堆栈内存并复制Vector类的成员字段。

第二个版本不会复制Vector对象。相反,将为两个引用分配内存(每个可能大约4或8个字节,具体取决于您的计算机)并填充调用方的Vector对象的地址。这是一个更少的内存分配和更少的写入。

第三个版本可能不再具有高性能。 const关键字对于确保代码执行时没有意外的副作用很有用,因此使用它可能是一种好习惯,但不太可能导致更快的代码。编译器可以使用const作为提示进行一些优化,但它仍然可以执行这些优化。

在您的情况下,Vector类非常小,除非您拨打大量电话,否则不太可能存在任何实际差异。理解第一版和第二版之间的不同语义更为重要。在第一个版本中,对ab的更改不会影响调用者对这些对象的视图。在第二个版本(包含引用)中,对ab 执行的更改会影响调用方的视图。

长话短说:首先获得语义,然后担心优化。警惕过早优化。如果你真的想要优化这类东西,那就找一本关于C ++内部的好书,深入理解编译器在遇到pass-by-value和pass-by时的确切行为 - 参考函数。

在您的情况下,我建议使用版本3,因为'const'显示您的意图并通过引用传递删除不必要的副本。

编辑:templatetypedef说它更好。