c++ 作为常量和通过引用传递-值得吗?[重复]

xn1cxnb4  于 2022-12-24  发布在  其他
关注(0)|答案(6)|浏览(146)
    • 此问题在此处已有答案**:

11年前关闭了。

    • 可能重复:**

How to pass objects to functions in C++?
在我的游戏中,我过度使用了数学向量和运算符重载。

class Vector
{
   float x, y;
};

这基本上就是我的Vector类的全部内容(不包括方法)。
我不是C++方面的Maven,我已经看到并阅读了关于作为常量传递和通过引用传递的内容。
那么,下面代码示例中的性能差异在哪里呢?

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;
}
  • 我应该使用这三个中的哪一个,为什么?
  • 对于编译器的优化过程,每个选项都有哪些优点?
  • 何时何地我必须特别小心?
bmvo0sr5

bmvo0sr51#

通过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引用来传递。需要注意的一点是,跟随引用与跟随指针非常相似(事实上,大多数引用的实现只是把它们当作自动解引用的指针),所以每次通过引用访问数据时,可能会有一个小的性能下降,只有分析才能告诉你这是否是一个大问题,尽管如此,我不会担心除非你有明确的理由认为是我的错。
这个函数的最终版本接受了Vectorconst的引用,就像通过常规引用传递一样,避免了任何复制。但是,当接受Vectorconst的引用时,禁止对函数内部的Vector进行任何更改。因此客户端可以假设X1 M17 N1 X将不被修改。(是的,从技术上讲,如果它写得不好或有mutable数据成员,则可以对其进行修改,但我们现在忽略它,这里重要的是高级思想),如果你想检查函数中的值,而不复制它,也不改变它,这个选项会很好。
通过引用传递和通过const引用传递之间还有一个区别,这就是函数对右值的作用,如果你有一个临时对象,或者你通过写Vector()显式地创建了它,或者通过做一些数学运算,比如写v1 + v2那么你就不能把这个临时的Vector传递给一个通过引用获取参数的函数,因为引用只能绑定到左值,如果你有这样一个函数:

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

那就没意义写了

DoSomething(v1 + v2);

因为这将改变临时表达式的x字段。为了防止这种情况,编译器将拒绝编译这段代码。
然而,C++有一个例外,它允许你把右值传递给通过const引用获取参数的函数,因为直观上,你不应该通过const引用修改一个对象,因此下面的代码是完全法律的的:

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

DoSomething(v1 + v2);

所以,总结一下-
1.按值传递和按const引用传递意味着类似的事情-您希望能够查看值而不能修改它。
1.任何时候你可以使用传值传递,你可以使用传-const-引用而不影响程序的正确性,然而,在间接引用和复制和析构参数的开销之间存在性能权衡。
1.应使用Pass-by-non-const-reference来指示“我想修改参数”。
1.不能将右值传递给通过非const引用获取其参数的函数。

e0uiprwp

e0uiprwp2#

是否要复制传递给函数的对象?如果按值传递对象"Vector a""Vector b",则必须处理复制它们的开销。对于小结构,开销可以忽略不计。
如果你通过引用或指针传递对象,你就不会有这样的开销。但是,通过指针或引用传递对象可能会允许修改对象:
对象名前面的const关键字用于保证您的函数不会修改通过引用或指针传递给该函数的对象。这不仅会告诉其他程序员您的函数可以安全使用,而且编译器也会严格执行它。const关键字在用于此目的时不会影响性能。
如果你有大型对象,可以通过const referenceconst pointer传递。如果你想修改传递给函数的对象,可以使用引用或指针。如果你的对象是小型结构,可以通过值传递。

ct2axkht

ct2axkht3#

你会得到这个问题的很多答案,声称这样或那样。事情的真相是你需要测试它。你的对象相当小,所以通过值传递可能和通过引用传递一样快或更快。只有通过分析你的代码,你才能知道。

ztigrdn8

ztigrdn84#

只有当您想更改参数并让客户端观察这些更改时,才通过引用传递非常数。
如果你不想改变参数,可以通过引用传递给const或者通过值传递。
如果要更改参数但不影响客户端,则按值传递。

c9x0cxw0

c9x0cxw05#

现在事情可能发生了变化,但我在很多年前用几个C++编译器和不同的变体(例如,成员或非成员运算符,operator+是否根据operator+=定义,按值传递或按引用传递等)做了一些测试。
我检查了时间和生成的汇编代码。结果高度依赖于使用的编译器...一个编译器最好,另一个编译器却不是最好的。
而且,很遗憾,在那个时候我从来没有能够得到相同的性能手动展开C代码执行相同的计算。关闭是的...但C仍然有点快。

lx0bsm1f

lx0bsm1f6#

简短的回答是,在实践中不会有什么不同。
下面是详细的答案:
第一个和第二个版本之间会有性能上的差异,第二个和第三个版本之间 * 可能 * 会有差异。
当你调用第一个版本(Vector a, Vector b)时,每个参数的副本都会在堆栈上生成,这涉及到分配堆栈内存和复制Vector类的成员字段。
第二个版本不会复制Vector对象,而是为这两个引用分配内存(根据您的机器,每个引用可能为4或8字节),并使用调用者的Vector对象的地址填充内存。这会减少内存分配和写操作。
第三个版本的性能可能不会更好。const关键字对于确保代码执行时没有意外的副作用非常有用,所以使用它可能是一个很好的实践,但不太可能导致代码更快。编译器 * 可以 * 使用const作为一个提示,允许一些优化,但无论如何它可能会执行这些优化。
在您的例子中,Vector类非常小,除非您进行大量调用,否则不太可能有任何实际差异。理解第一个版本和第二个版本之间的不同语义要重要得多。对ab的更改不会影响调用者对这些对象的视图。(带引用)对ab的更改会影响调用方的视图。
长话短说:首先要做好语义,然后再考虑优化。警惕过早的优化。如果你真的想优化这类东西,那么就去读一本关于C++内部的好书,深入了解当编译器遇到传值和传引用函数时,它到底会做些什么。
在您的情况下,我建议使用版本3,因为"const"显示了您的意图,并且通过引用传递删除了不必要的副本。
编辑:templatypedef说得更好。

相关问题