提问人: 提问时间:4/16/2009 最后编辑:8 revs, 5 users 88%Nick Bolton 更新时间:11/1/2022 访问量:373057
返回 C++ 引用变量的做法是邪恶的吗?
Is the practice of returning a C++ reference variable evil?
问:
我认为这有点主观;我不确定意见是否会一致(我已经看到很多返回引用的代码片段)。
根据我刚才提出的这个问题的评论,关于初始化引用,返回引用可能是邪恶的,因为[据我所知]它更容易错过删除它,这可能导致内存泄漏。
这让我很担心,因为我遵循了例子(除非我在想象事情)并在相当多的地方这样做了......我误会了吗?是邪恶的吗?如果是这样,到底有多邪恶?
我觉得由于我的指针和引用混合在一起,再加上我是 C++ 的新手,并且对何时使用什么完全感到困惑,我的应用程序一定是内存泄漏地狱......
此外,我知道使用智能/共享指针通常被认为是避免内存泄漏的最佳方法。
答:
这不是邪恶的。就像 C++ 中的许多东西一样,如果使用得当,它很好,但是在使用它时应该注意许多陷阱(例如返回对局部变量的引用)。
用它可以实现一些好东西(比如 map[name] = “hello world”)
评论
map[name] = "hello world"
HashMap<String,Integer>
不。
邪恶的是引用动态分配的对象并丢失原始指针。当你一个对象时,你承担了一个有义务得到保证。new
delete
但是看一下,例如,:必须返回引用,或者operator<<
cout << "foo" << "bar" << "bletch" << endl ;
行不通。
评论
它不仅不是邪恶的,有时还是必不可少的。例如,如果不使用引用返回值,就不可能实现 std::vector 的 [] 运算符。
评论
operator[]
std::vector<bool>
::std::vector<bool>
std::vector<T>
T
bool
std::vector<bool>
std::vector
通常,返回引用是完全正常的,并且一直在发生。
如果你的意思是:
int& getInt() {
int i;
return i; // DON'T DO THIS.
}
那是各种邪恶。堆栈分配将消失,您什么也没引用。这也是邪恶的:i
int& getInt() {
int* i = new int;
return *i; // DON'T DO THIS.
}
因为现在客户最终必须做奇怪的事情:
int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt; // must delete...totally weird and evil
int oops = getInt();
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original
请注意,右值引用仍然只是引用,因此所有邪恶的应用程序都保持不变。
如果要分配超出函数范围的内容,请使用智能指针(或通常为容器):
std::unique_ptr<int> getInt() {
return std::make_unique<int>(0);
}
现在,客户端存储了一个智能指针:
std::unique_ptr<int> x = getInt();
引用也可用于访问您知道生存期在更高级别上保持开放的内容,例如:
struct immutableint {
immutableint(int i) : i_(i) {}
const int& get() const { return i_; }
private:
int i_;
};
在这里,我们知道返回引用是可以的,因为任何调用我们的东西都会管理类实例的生存期,因此至少会存在那么长时间。i_
i_
当然,只是:
int getInt() {
return 0;
}
如果生存期应该留给调用方,而你只是在计算值。
摘要:如果对象的生存期在调用后没有结束,则可以返回引用。
评论
return new int
。
有两种情况:
const reference --好主意,有时,特别是对于重对象或代理类,编译器优化
non-const reference --坏主意,有时会破坏封装
两者都有相同的问题 - 可能指向被破坏的物体......
我建议在需要返回引用/指针的许多情况下使用智能指针。
此外,请注意以下事项:
有一个正式的规则 - C++ 标准(如果您有兴趣,第 13.3.3.1.4 节)规定临时只能绑定到 const 引用 - 如果您尝试使用非常量引用,编译器必须将其标记为错误。
评论
“返回引用是邪恶的,因为, 简单地[据我所知]它做到了 更容易错过删除它”
不对。返回引用并不意味着所有权语义。也就是说,仅仅因为你这样做:
Value& v = thing->getTheValue();
...并不意味着您现在拥有 v 所指的内存;
然而,这是可怕的代码:
int& getTheValue()
{
return *new int;
}
如果你这样做是因为“你不需要该实例上的指针”,那么:1)如果你需要引用,只需取消引用指针,2)你最终将需要指针,因为你必须将new与delete匹配,并且你需要一个指针来调用delete。
您应该返回对现有对象的引用,该对象不会立即消失,并且您不打算进行任何所有权转移。
永远不要返回对局部变量或类似变量的引用,因为它不会被引用。
您可以返回对独立于函数的内容的引用,您不希望调用函数负责删除该内容。典型功能就是这种情况。operator[]
如果你正在创建一些东西,你应该返回一个值或指针(常规或智能)。您可以自由返回一个值,因为它将进入调用函数中的变量或表达式。永远不要返回指向局部变量的指针,因为它会消失。
评论
作为左值的函数(也就是返回非常量引用)应该从 C++ 中删除。这非常不直观。斯科特·迈耶斯(Scott Meyers)想要一个具有此行为的min()。
min(a,b) = 0; // What???
这并不是真正的改进
setmin (a, b, 0);
后者甚至更有意义。
我意识到函数作为左值对于 C++ 样式的流很重要,但值得指出的是,C++ 样式的流很糟糕。我不是唯一一个这么想的人......我记得 Alexandrescu 有一篇关于如何做得更好的文章,我相信 boost 也试图创建一种更好的类型安全 I/O 方法。
评论
vector::operator[]
v.setAt(i, x)
v[i] = x
v.setAt(i, x)
我遇到了一个真正的问题,它确实是邪恶的。从本质上讲,开发人员返回了对矢量中对象的引用。这很糟糕!!
我在 Janurary 中写过的全部细节:http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html
评论
关于可怕的代码:
int& getTheValue()
{
return *new int;
}
因此,确实,内存指针在返回后丢失。但是,如果您像这样使用shared_ptr:
int& getTheValue()
{
std::shared_ptr<int> p(new int);
return *p->get();
}
返回后内存不会丢失,分配后将释放。
评论
return reference 通常用于 C++ 中大型 Object 的运算符重载,因为返回值需要复制操作。(在 perator 重载中,我们通常不使用指针作为返回值)
但是返回引用可能会导致内存分配问题。由于对结果的引用将作为对返回值的引用从函数中传出,因此返回值不能是自动变量。
如果要使用返回引用,则可以使用静态对象的缓冲区。 例如
const max_tmp=5;
Obj& get_tmp()
{
static int buf=0;
static Obj Buf[max_tmp];
if(buf==max_tmp) buf=0;
return Buf[buf++];
}
Obj& operator+(const Obj& o1, const Obj& o1)
{
Obj& res=get_tmp();
// +operation
return res;
}
这样,您可以安全地使用返回引用。
但是你总是可以使用指针而不是引用来返回函数中的值。
我认为使用引用作为函数的返回值比使用指针作为函数的返回值要简单得多。 其次,使用返回值所引用的静态变量始终是安全的。
最好的办法是创建对象并将其作为引用/指针参数传递给分配此变量的函数。
在函数中分配对象并将其作为引用或指针返回(但指针更安全)是一个坏主意,因为在功能块结束时释放内存。
我发现答案不令人满意,所以我会加两分钱。
下面我们来分析一下以下案例:
错误用法
int& getInt()
{
int x = 4;
return x;
}
这显然是错误的
int& x = getInt(); // will refer to garbage
与静态变量一起使用
int& getInt()
{
static int x = 4;
return x;
}
这是正确的,因为静态变量在程序的整个生命周期中都存在。
int& x = getInt(); // valid reference, x = 4
这在实现单例模式时也很常见
class Singleton
{
public:
static Singleton& instance()
{
static Singleton instance;
return instance;
};
void printHello()
{
printf("Hello");
};
};
用法:
Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
my_sing.printHello(); // "Hello"
运营商
标准库容器在很大程度上依赖于返回引用的运算符的使用,例如
T & operator*();
可用于以下情况
std::vector<int> x = {1, 2, 3}; // create vector with 3 elements
std::vector<int>::iterator iter = x.begin(); // iterator points to first element (1)
*iter = 2; // modify first element, x = {2, 2, 3} now
快速访问内部数据
有时 & 可用于快速访问内部数据
Class Container
{
private:
std::vector<int> m_data;
public:
std::vector<int>& data()
{
return m_data;
}
}
用法:
Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1
但是,这可能会导致如下陷阱:
Container* cont = new Container;
std::vector<int>& cont_data = cont->data();
cont_data.push_back(1);
delete cont; // This is bad, because we still have a dangling reference to its internal data!
cont_data[0]; // dangling reference!
评论
true
If((a*b) == (c*d))
Container::data()
的实现应改为return m_data;
Class Set {
int *ptr;
int size;
public:
Set(){
size =0;
}
Set(int size) {
this->size = size;
ptr = new int [size];
}
int& getPtr(int i) {
return ptr[i]; // bad practice
}
};
getPtr 函数可以在删除后访问动态内存,甚至可以访问空对象。这可能会导致错误访问异常。相反,在返回之前,应实现 getter 和 setter 并验证大小。
除了已接受的答案之外:
struct immutableint { immutableint(int i) : i_(i) {} const int& get() const { return i_; } private: int i_; };
我认为这个例子是不行的,如果可能的话应该避免。为什么?很容易得到一个悬空的参考。
举个例子来说明这一点:
struct Foo
{
Foo(int i = 42) : boo_(i) {}
immutableint boo()
{
return boo_;
}
private:
immutableint boo_;
};
进入危险区域:
Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!
下一个:使用成员初始化成员
评论