我们都知道returning a reference to a local variable is a bad idea。但是,我想知道返回引用是否真的是一个好主意,如果有可能确定一些关于何时或何时不执行此操作的良好规则。
返回引用的问题在于,调用函数需要关心不应该负责的对象的生命周期。作为一个人为的例子:
#include <vector>
const int& foo() {
std::vector<int> v = {1, 2, 3, 4, 5};
return v[0];
}
int main(int argc, const char* argv[])
{
const int& not_valid = foo();
return 0;
}
这里,vector
超出了foo
末尾的范围,破坏了其内容并使对其元素的任何引用无效。 vector::operator[]
会返回对该元素的引用,因此当foo
中进一步返回此引用时,main
中的引用将悬空。我不相信const引用会延长这里的生命周期,因为它不是对临时引用的引用。
正如我所说,这是一个人为的例子,foo
的作者可能不会那么愚蠢地尝试返回v[0]
作为参考。但是,很容易看出返回引用如何要求调用者关心它不拥有的对象的生命周期。将元素推入vector
复制它,然后vector
负责它。传递引用参数不存在此问题,因为您知道函数将在调用者继续并销毁对象之前完成。
我可以看到返回一个引用允许一些类似于v[0] = 5
的类似数组的语法 - 但是有一个像v.set(index, value)
这样的成员函数有什么不好?至少我们不会暴露内部对象。我知道返回引用可能会有性能提升,但使用RVO,命名RVO(NRVO)和移动语义,它可以忽略不计或不存在。
所以我一直试图想象在什么情况下返回引用真的是安全的,但是我无法理解它可能涉及的所有权语义的所有不同排列。关于何时这样做有什么好的规则吗?
注意:我知道在vector
中处理所有权的更好方法是使用智能指针,但是你会遇到另一个对象的问题 - 谁拥有智能指针?
答案 0 :(得分:10)
返回引用有很多好的用途。正如你所说的那样,一个是模仿原生的解引用算子:
struct Goo
{
int & operator[](size_t i) { return arr[i]; }
int & front() { return arr[0]; }
// etc.
private:
int * arr;
};
另一个用例是当你返回对传入的东西的引用时。典型的例子是像<<
这样的可链接操作:
std::ostream & operator<<(std::ostream & os, Goo const & g)
{
return os << g[3];
}
作为最后一个例子,这是一个线程安全的全局对象:
Goo & get_the_object()
{
static Goo impl;
return impl;
}
引用是语言不可或缺的一部分,它们很可能通过函数调用返回。正如你所说,了解对象的生命周期很重要,但这始终是正确的,而不是返回引用的特定问题。
答案 1 :(得分:0)
就个人而言,当我想实现Singleton模式时,我喜欢返回对静态变量的引用
SomeClass& getTheSingleton()
{
static SomeClass theInstance;
return theInstance;
}
我不必编写任何涉及是否初始化某个指针的逻辑,它让我对静态初始化的顺序有所控制