考虑以下两个代码示例:
void int_fun(int val) {}
void another_fun() {
vector<int> test(1, 1);
const int& int_ref = test[0];
int_fun(int_ref);
}
与
void int_fun(int val) {}
void another_fun() {
vector<int> test(1, 1);
const int int_val = test[0];
int_fun(int_val);
}
编译器是否可以将int_ref实现为无操作,而在第二种情况下,它必须创建test [0]的副本?或者也可以在第二种情况下优化复制,这两个样本的性能相当吗?
示例和问题的动机是,有时为了代码清晰,创建本地对象是有益的,这些对象会立即传递给某些函数,如示例所示。
使用基本类型的引用(例如int,bool等)是否会在这种情况下提供任何好处,或者引用和值是等价的(或者值可能更好)?
答案 0 :(得分:2)
在你给予的情况下,我看不到真正的优势。我怀疑大多数编译器在大多数情况下会为这两个编译器生成相同的代码(至少启用了优化)。我认为引用使得意图更加清晰:真正的意图是将原始对象传递给函数,并且您只是出于某种原因创建并传递别名到该对象。
在略微不同的情况下,行为可能会有真正的不同。显而易见的是,如果函数收到了引用:
void int_fun(int &val);
void another_fun() {
vector<int> test(1, 1);
int &int_val = test[0];
int_fun(int_val);
}
在这种情况下,int_fun
可能会修改它接收引用的值,因此如果您创建了引用,然后通过引用传递,则引用将被折叠,因此该函数可以修改数组(但是如果您创建了一个副本,然后通过引用传递它,那么副本将被修改而不是原始版本。)
答案 1 :(得分:2)
您展示的代码示例是微优化(或微观优化,无论情况如何),因为在现代硬件上制作单个原始副本的成本非常低廉;同样适用于引用基元。与调用任何感兴趣的函数的成本相比,成本会逐渐消失。
然而,对您的示例进行的一个非常简单的更改演示了在对基元进行引用变得有益时的情况,因为您可以将它们分配回来。
以下是您的示例已修改为使用std::map<std::string,int>
代替std::vector<int>
:
std::map<std::string,int> cache;
int compute_new(int old) {
return old+1;
}
void fun_with_ref(const std::string& key) {
int& int_ref = cache[key];
int_ref = compute_new(int_ref);
}
void fun_with_val(const std::string& key) {
int int_val = cache[key];
cache[key] = compute_new(int_val);
}
请注意fun_with_ref
key
执行单个查找的方式,而fun_with_val
需要两次查找。 std::map<std::string,ing>
的访问时间增长为O(log 2 N),因此当地图增长到大尺寸时,节省可能会变得很大。
quick micro-benchmark表示使用带有1,000,000个条目的地图的引用的代码几乎是使用值的代码的两倍。
vector<string> keys;
for (int i = 0 ; i != 1000000 ; i++) {
auto key = to_string(i);
cache[key] = i;
keys.push_back(key);
}
auto ms1 = duration_cast< milliseconds >(
system_clock::now().time_since_epoch()
).count();
for (const string& key : keys) {
fun_with_ref(key);
}
auto ms2 = duration_cast< milliseconds >(
system_clock::now().time_since_epoch()
).count();
for (const string& key : keys) {
fun_with_val(key);
}
auto ms3 = duration_cast< milliseconds >(
system_clock::now().time_since_epoch()
).count();
cout << "Ref: " << (ms2-ms1) << endl;
cout << "Val: " << (ms3-ms2) << endl;
输出:
Ref: 557
Val: 1064
答案 2 :(得分:0)
清晰的代码。对于非平凡的代码,有时使用对容器成员的引用可以获得更清晰的代码。此外,我可以在向智能指针传递函数时执行此操作。在函数内部,我将通过将其分配给引用并在整个函数中使用该引用来取消引用它一次。 这样做可以使它更清晰,避免复制对象。对于内置类型,我只使用一个赋值并避免引用。
如果您确实需要每次性能,那么您可以查看生成的汇编语言代码。然后,描述它。
答案 3 :(得分:-1)
使用对本地值的引用有时很有用,即使它们是基本类型,如果它们位于更复杂的类型中,那么您可以将引用用作实数变量的读/写快捷方式。这里的诀窍是 write 部分。如果不需要修改变量,那么复制值也一样好。
以一个更复杂的代码为例:
void fun()
{
vector<int> test;
//fill test with a lot of values
for (int i=0; i < test.size(); ++i)
{
int &x = test[i]; //<-- handy reference!
if (x > i)
++x;
else if (x < i)
--x;
}
}
如果你想在没有引用的情况下编写相同的逻辑,你必须至少使用vector::operator[](int)
两次!
将vector
更改为map
,事情更有趣:
void fun()
{
map<string, int> keys;
//fill keys
int &x = keys["foo"];
++x;
}