在C++中返回对局部变量的引用

144

如果需要返回 i,下面的代码 (func1()) 是否正确?我记得在某个地方读到过当返回局部变量的引用时会有问题。那么这和 func2() 有什么不同呢?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

1
如果你将func1()修改为使用动态分配的内存,则它们是相同的 :-) int& i = * new int; - Martin York
2
与const局部变量相关:https://dev59.com/5nE85IYBdhLWcg3wbS1h - Ciro Santilli OurBigBook.com
3个回答

227
该代码片段:
int& func1()
{
    int i;
    i = 1;
    return i;
}

由于你返回了一个别名(引用)指向在函数调用作用域内受限的对象,因此不会起作用。这意味着一旦func1()返回,int i就会被销毁,使得从函数返回的引用变得无用,因为它现在指向一个不存在的对象。

int main()
{
    int& p = func1();
    /* p is garbage */
}

第二个版本是有效的,因为变量是在自由存储区域分配的,而不是绑定到函数调用的生命周期上。但是,你需要负责删除所分配的 int

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

通常情况下,您会将指针包装在某个RAII类和/或工厂函数中,这样您就不必自己delete它。

无论哪种情况,您都可以只返回该值本身(尽管我意识到您提供的示例可能是虚构的):

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

请注意,像 func3() 返回原始值那样返回大型对象是完全可以的,因为现在几乎所有的编译器都实现了某种形式的返回值优化

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

有趣的是,将一个临时变量绑定到const引用在C++中是完全合法的。

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}

2
漂亮的解释。:hattip: 在第三个代码片段中,您正在删除 int* p = func2(); delete p; 现在,当您删除 'p' 时,这是否意味着在函数 func2() 的定义内分配的内存也被删除了? - Aquarius_Girl
2
@Anisha Kaul:是的。内存是在func2()内分配的,并在下一行外释放的。虽然这是一种容易出错的处理内存的方式,但正如我所说,你应该使用某种RAII变体。顺便说一句,你听起来像是在学习C++。我建议你阅读一本好的入门C++书籍进行学习。此外,以后如果你有问题,可以在Stack Overflow上发布问题。评论不适合用于提问全新的问题。 - In silico
你修改了答案吗?:mad: 我很容易就错过了。;) ;) - Aquarius_Girl
@Anisha Kaul:没有,我没有。根据我的帖子下的时间戳,我上次编辑回答是在1月10日。 - In silico
哦哦!在“答案”中,我指的是你的“评论”,那对我来说就是一个答案。你的编辑是:“此外,作为以后的参考,如果有问题,您可以在 Stack Overflow 上发布问题。评论并不适用于提出全新的问题”这非常有帮助,我以前总是害怕创建相似的新问题。顺便说一下,你可以直接写 @Anisha,而不是写全名。 - Aquarius_Girl
显示剩余3条评论

23

局部变量是存储在栈上的内存空间,当超出范围时这块内存不会被自动失效。从更深层次的函数中(在内存中较高的位置)访问这块内存是完全安全的。

一旦函数返回并结束了,情况就变得危险起来。 通常情况下,在返回时内存不会被删除或覆盖,这意味着该地址处的内存仍然包含您的数据-指针看起来是有效的。

直到另一个函数建立起栈并将其覆盖为止。 这就是为什么这种方式能够工作一段时间的原因-但在一个特别深奥的函数集或有很多本地对象的函数之后,它突然停止工作。

甚至可能发生您再次到达同一程序部分,并使用新函数变量覆盖旧的局部函数变量。所有这些都非常危险,应该严厉禁止。

不要使用指向局部对象的指针!


2
记住这些简单规则,它们适用于参数和返回类型:
  • Value - 复制所讨论的项目。
  • Pointer - 引用所讨论的项目的地址。
  • Reference - 就是所讨论的项目。
每种情况都有其适用的时机和场合,所以一定要了解它们。局部变量只在函数范围内存在,如您在此处所示。在您的示例中,如果返回类型为int*并返回&i,同样是不正确的。在这种情况下,最好这样做:
void func1(int& oValue)
{
    oValue = 1;
}

这样做会直接更改您传入参数的值。而这段代码...
void func1(int oValue)
{
    oValue = 1;
}

不会改变oValue的值,它只会改变函数调用中本地的oValue的值。原因是你实际上只是在改变oValue的“本地”副本,而不是oValue本身。


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