传递引用的开销是什么?

8

当getter返回引用时,访问成员变量的代价有多高?

例如,如果一个类需要经常使用这样的访问器,将该引用存储在需要使用它的类中并仅初始化一次,效率会提高多少?


3
如果您在标记为[编码风格]的问题中询问有关性能开销的问题... - BoltClock
6
与什么相比,开销才算高? - GManNickG
1
与将引用变量存储在类中并引用它相比,我在问题中确实提到了这一点。 - Dollarslice
4个回答

7
关于复杂性,返回或传递引用就像传递指针一样。它的开销相当于传递一个指针大小的整数加上几个指令。简而言之,在几乎所有情况下,这是尽可能快的。内置类型(例如int、float)小于或等于指针大小是明显的例外。
最坏的情况下,传递/返回引用可能会增加几个指令或禁用一些优化。这些损失很少超过通过值返回/传递对象的成本(例如调用复制构造函数+析构函数要高得多,即使是对于非常基本的对象)。传递/返回引用是一个很好的默认选择,除非每个指令都很重要,并且您已经测量了差异。
因此,使用引用的开销极低。
无法真正量化不知道您的类型以及它们的构造函数/析构函数的复杂度有多快,但如果不是内置类型,那么在大多数情况下保持本地并通过引用返回它将是最快的-这完全取决于对象的复杂性和其副本,但只有非常简单的对象才能接近引用的速度。

5

一般情况下,函数不应该返回引用。原因是这会使成员变量对外公开 - 如果你想这样做,请将其设置为公共成员。

class foo {
  int bar;
  public:
    int& get_bar() { return bar; } // this is silly! Just make bar public!
}

无论如何,如果像 get_bar 这样简单,它将被内联为类似 foo.bar 的东西。正如 Oli 所指出的那样,您也可以将其作为常量引用,但对于像 int 这样的小型类型,您应该继续按值返回。

对于更昂贵的类型,引用开始变得有益。您可以假设:

  • 值的“开销”基于您要返回的内容的大小
  • 引用的“开销”基于引用的大小和解引用成本

例如:

foo x;
x.get_value().func(); // calls copy constructor at least once and destructor
x.get_reference().func(); // may require dereference when using

4
你可以返回一个const int &。但更重要的是,这将内部细节和接口耦合在一起。(不过我不会给它点赞,因为它并没有回答问题...) - Oliver Charlesworth
虽然我基本上同意这种观点,但是有合理的理由返回引用。std::complex可能是一个值得注意的例子,因为目前只能通过标准使用reinterpret_cast(这不是很好的实践方法,也不支持constexpr)来进行低级别访问和保证。请注意,std::memcpy和std::bit_cast不返回引用,因此不能用于修改底层数据。但是std::complex本身也存在问题。一个设计良好的API不应该像std::complex那样。 - Christopher Mauer

1
如果函数定义可用且相对简单,则此类函数将被内联,与直接访问成员相比没有任何开销。否则,操作序列就像获取类/结构对象的地址,应用偏移量以获取成员的地址,并将此地址返回给调用者一样简单。现在,对于那些情况,仅在对象不可复制或其大小大于指针大小时才有意义通过引用返回。非可复制对象的原因是显而易见的,否则您会看到复制开销-结构体大小与指针大小之间的差异。因此,经验法则是 - 通过引用(或指针)返回大型对象,通过复制小型对象(整数,双精度等)。在那些您不必在整个程序中控制成员访问权限的情况下 - 只需使用具有公共成员访问权限的结构即可,不要用大量的getter和setter膨胀您的代码。

1
坦白地说,如果你开始考虑这个开销:你是否已经考虑过调用约定,使用stdcall而不是cdecl?
从中获得的速度提升与你讨论这个问题时所说的类似。

我对这没有任何了解,您可以详细说明一下吗? - Dollarslice
这篇MSDN文章展示了不同的调用约定。你应该先阅读一些关于它们的信息,谷歌上有很多资料。但我想说的是,对此感到好奇只是过早优化。 - paul23

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接