对 int 的本地引用有什么好处吗?
Does local reference to int provide any benefit?
考虑以下两个代码示例:
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 等)使用引用是否有任何好处,或者引用和值是等价的(或者值可能更好)?
使用对局部值的引用有时很有用,即使它们是基本类型,如果它们在更复杂的类型中,这样您就可以将引用用作实际变量的 read/write 快捷方式。这里的技巧是 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;
}
为了清晰起见,代码。对于重要代码,有时使用对容器成员的引用可以使代码更清晰。此外,我可能会在将智能指针传递给函数时执行此操作。在函数内部,我将通过将其分配给一个引用来取消引用一次,并在整个函数中使用该引用。
这样做的目的是使它更清晰并避免复制对象。对于内置类型,我只使用赋值并避免引用。
如果你真的需要每一次的性能,那么你可以看看产生的汇编语言代码。然后,对其进行分析。
在你给的情况下,我看不出真正的优势。我怀疑大多数编译器在大多数情况下都会为两者生成相同的代码(至少在启用优化的情况下)。我认为该引用使意图更加清晰:真正的意图是将原始对象传递给函数,而您只是出于某种原因创建并传递了一个别名给该对象。
在略有不同的情况下,行为可能会有所不同。显而易见的是,如果函数收到引用:
void int_fun(int &val);
void another_fun() {
vector<int> test(1, 1);
int &int_val = test[0];
int_fun(int_val);
}
在这种情况下,int_fun
可能会修改它收到引用的值,因此如果您创建了一个引用,然后通过引用传递它,引用将被折叠,因此该函数可以修改数组中的值(但如果您创建了一个副本,然后通过引用传递它,则会修改副本而不是原始副本)。
您展示的代码示例是微优化(或微去优化,不管是什么情况),因为在现代硬件上制作原语的单个副本的成本非常低;引用原语也是如此。与调用执行任何感兴趣的函数的成本相比,成本逐渐降低。
但是,对您的示例进行的一个非常简单的更改演示了一种情况,当引用原语变得有益时,因为您可以将它们分配回去。
这是您的示例,修改为使用 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(log2N),因此当地图增长到大尺寸时,节省的时间可能会变得很可观。
A 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
考虑以下两个代码示例:
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 等)使用引用是否有任何好处,或者引用和值是等价的(或者值可能更好)?
使用对局部值的引用有时很有用,即使它们是基本类型,如果它们在更复杂的类型中,这样您就可以将引用用作实际变量的 read/write 快捷方式。这里的技巧是 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;
}
为了清晰起见,代码。对于重要代码,有时使用对容器成员的引用可以使代码更清晰。此外,我可能会在将智能指针传递给函数时执行此操作。在函数内部,我将通过将其分配给一个引用来取消引用一次,并在整个函数中使用该引用。 这样做的目的是使它更清晰并避免复制对象。对于内置类型,我只使用赋值并避免引用。
如果你真的需要每一次的性能,那么你可以看看产生的汇编语言代码。然后,对其进行分析。
在你给的情况下,我看不出真正的优势。我怀疑大多数编译器在大多数情况下都会为两者生成相同的代码(至少在启用优化的情况下)。我认为该引用使意图更加清晰:真正的意图是将原始对象传递给函数,而您只是出于某种原因创建并传递了一个别名给该对象。
在略有不同的情况下,行为可能会有所不同。显而易见的是,如果函数收到引用:
void int_fun(int &val);
void another_fun() {
vector<int> test(1, 1);
int &int_val = test[0];
int_fun(int_val);
}
在这种情况下,int_fun
可能会修改它收到引用的值,因此如果您创建了一个引用,然后通过引用传递它,引用将被折叠,因此该函数可以修改数组中的值(但如果您创建了一个副本,然后通过引用传递它,则会修改副本而不是原始副本)。
您展示的代码示例是微优化(或微去优化,不管是什么情况),因为在现代硬件上制作原语的单个副本的成本非常低;引用原语也是如此。与调用执行任何感兴趣的函数的成本相比,成本逐渐降低。
但是,对您的示例进行的一个非常简单的更改演示了一种情况,当引用原语变得有益时,因为您可以将它们分配回去。
这是您的示例,修改为使用 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(log2N),因此当地图增长到大尺寸时,节省的时间可能会变得很可观。
A 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