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

Yuk*_*kon 36 c++

可能重复:
如何将对象传递给C++中的函数?

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

class Vector
{
   float x, y;
};
Run Code Online (Sandbox Code Playgroud)

这基本上都是关于我的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;
}
Run Code Online (Sandbox Code Playgroud)
  • 我应该使用三个中的哪一个,为什么?
  • 每个选项对编译器的优化过程有什么好处?

  • 我何时何地都要特别小心?

tem*_*def 125

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

如果你考虑这三个功能:

void VersionOne(Vector v);
void VersionTwo(Vector& v);
void VersionThree(const Vector& v);
Run Code Online (Sandbox Code Playgroud)

所有这些之间都有微妙的区别.例如,第一个函数将在传入a时调用复制构造函数,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;
}
Run Code Online (Sandbox Code Playgroud)

然后编写没有意义

DoSomething(v1 + v2);
Run Code Online (Sandbox Code Playgroud)

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

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

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

DoSomething(v1 + v2);
Run Code Online (Sandbox Code Playgroud)

所以,总结一下 -

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

希望这可以帮助!

  • 谢谢你的回答,我偶然发现了这片绿洲,渴望获得这些知识. (7认同)
  • 对于任何贬低的人 - 你能解释一下原因吗?我认为这是一个非常好的解释,如果出现错误,我会很感激评论它有什么问题. (5认同)
  • `const HugeObject &amp;ref` 与 `HugeObject &amp;ref` 之间有性能差异吗?换句话说,第二个和第三个功能之间有性能差异吗? (2认同)

Maa*_*ten 8

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

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

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

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


Edw*_*nge 5

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