每个const字符串都被引用是一种好的做法吗?

4

代码A:

string a = "Hello World";

for (int i=0; i<1000000; ++i) 
     const string b = a;

代码 B:

string a = "Hello World";

for (int i=0; i<1000000; ++i) 
     const string &b = a;

如果我分别运行这两段代码并且测量CPU时间,Code B的速度比Code A快大约2.5倍。

除了像charintfloat等原始类型以外,我学到获取原始引用会比复制它更快。

虽然在大多数情况下差异几乎可以忽略不计,但是是否可以考虑将const string类型(以及其他非原始类型)总是指向引用作为良好的惯例呢?


7
通常的指导原则如下:如果一个类型的sizeofsizeof(void*)大,那么始终通过const引用传递参数;否则就通过复制传递。如果您想要更改对象,则无论大小都应该传递非const引用。 - DeiDei
2
有时候会有例外情况--比如说你想要在函数内部复制参数,那么就需要通过值传递--这样隐式地完成了你想要的复制,而且还可以利用类似于移动操作的方式高效地实现。 - jaggedSpire
2
类可能会定义自定义的复制或赋值运算符(如字符串所做),因此 sizeof 并不总是一个好的复制时间指标。 - Daniel H
1
不是“每个”,你需要确保在引用对象(a)的生命周期内使用包含的引用(b)。 - Rama
2
你的编译器太差了。:) 两个代码都没有任何效果;它应该被简化为一个 ret 指令或其他指令。 - Kaz
显示剩余3条评论
2个回答

3

一般来说,对于任何昂贵的复制对象,最好使用const引用。但是您必须注意当您不希望发生变化时,原始对象不会更改;特别需要确保原始对象未被销毁,因为这会导致未定义的行为。在您的玩具示例中,这不是问题,但在现实世界中,这是一个大问题。

最好在函数参数中使用const引用。您知道传递到函数中的对象在函数内部无法更改或销毁。


2

有几个事情需要考虑:

  1. 您的用例。您想复制还是想观察?
  2. 该类型是否执行深度复制语义(例如,在复制时分配动态内存)?

如果您只想观察对象
如果对象的类型在复制时分配动态内存,则始终通过const引用传递。
否则,如果该类型的sizeof大于sizeof(void*),则通过const引用传递。
否则,通过值传递。

如果您想复制对象,则只需通过值传递即可。

当然,在奇怪的用例中,您可能会做其他事情,但这是我看到通常遵循的一般指南。

还有移动语义要考虑,但那是另一个问题。


2
你可能还想提一下,在C++17中,对于std::string,有std::string_view可以观察字符串。 - Rakete1111
一个有趣的数据点是 sizeof(string_view) 大约是 2*sizeof(void*),但在 C++17 标准库中 string_view 是按值传递的。 - Bo Persson
1
@BoPersson 我最近在查看libcxxstdlibc++std::any的实现。在libcxx中,他们认为“小对象”是3*sizeof(void*),而在stdlibc++中则是sizeof(void*)。我觉得这非常有趣。它也可以在通过值传递的标准迭代器中找到。一些容器(例如我的平台上的std::deque)具有32字节大小的迭代器。 - DeiDei

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