返回C ++参考变量的做法是否有害?


341

我认为这有点主观;我不确定这种意见是否会一致(我已经看到很多返回引用的代码段)。

根据这个问题的评论,关于初始化引用,返回引用可能是邪恶的,因为(据我所知),因为它更容易错过删除它,这可能导致内存泄漏。

这让我感到担心,因为我遵循了示例(除非我在想象中的事情),并且在相当多的地方进行了此操作……我是否误解了?是邪恶的吗?如果是这样,到底有多邪恶?

我觉得由于我的指针和引用混合在一起,再加上我是C ++的新手,再加上对何时使用什么的完全困惑,我的应用程序一定是内存泄漏地狱...

另外,我了解使用智能/共享指针通常被认为是避免内存泄漏的最佳方法。


如果您正在编写类似getter的函数/方法,那不是邪恶的。
John Z. Li

Answers:


411

通常,返回引用是完全正常的,并且始终会发生。

如果你的意思是:

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;
}

如果生存期应由调用者决定,那么您只是在计算值。

简介:如果对象的生存期在调用后不会结束,则可以返回引用。


21
这些都是不好的例子。正确用法的最佳示例是在返回对传入的对象的引用时
。ala

171
为了后代的缘故,对于任何较新的程序员而言,指针也不错。指向动态内存的指针也不坏。它们在C ++中都有其合法的位置。不过,在进行动态内存管理时,智能指针绝对应该是您的默认选择,但是默认智能指针应该是unique_ptr,而不是shared_ptr。
Jamin Gray

12
编辑批准者:如果您不能保证其正确性,则不要批准编辑。我回滚了不正确的编辑。
GManNickG 2014年

7
为了后代的缘故,以及任何较新的程序员都喜欢这样做return new int请不要编写
Lightness Races in Orbit 2015年

3
为了后代,以及任何较新的程序员喜欢这样做,只需从函数中返回T。RVO将照顾一切。
Shoe 2015年

64

不,不,不超过一千次

邪恶的是引用动态分配的对象并丢失了原始指针。当您new成为对象时,您将承担保证义务delete

但是看一下,例如operator<<必须返回一个引用,或者

cout << "foo" << "bar" << "bletch" << endl ;

将无法正常工作。


23
我之所以投票,是因为这既不能回答问题(OP明确表示他知道需要删除),也没有解决合理的担心,即返回对自由存储对象的引用会导致混乱。叹。

4
返回参考对象的做法不是邪恶的。所以没有 正如我在第二篇文章中指出的那样,他表达的恐惧是正确的恐惧。
查理·马丁

你实际上没有。但这不值得我花时间。

2
Iraimbilanja @关于“否”的问题-我不在乎。但是这篇文章指出了GMan缺少的重要信息。
Kobor42年

48

您应该返回对不会立即消失并且不打算进行所有权转移的现有对象的引用。

切勿返回对局部变量或此类变量的引用,因为它不会在那里被引用。

您可以返回对独立于该函数的引用,您不希望调用函数承担删除的责任。典型operator[]功能就是这种情况。

如果要创建内容,则应返回值或指针(常规或智能)。您可以自由返回一个值,因为它会进入调用函数中的变量或表达式中。永远不要返回指向局部变量的指针,因为它会消失。


1
很好的答案,但对于“您可以返回一个临时作为const引用”。以下代码将编译,但可能会崩溃,因为在return语句的末尾临时文件被破坏了:“ int const&f(){return 42;} void main(){int const&r = f(); ++ r;} “
j_random_hacker

@j_random_hacker:C ++对于引用临时对象有一些奇怪的规则,这些临时规则可能会延长临时生存期。对不起,我不太了解它是否可以解决您的问题。
Mark Ransom

3
@Mark:是的,它确实有一些奇怪的规则。只能通过初始化一个const引用(不是类成员)来延长临时对象的生存期。然后它一直存在,直到引用超出范围。可悲的是,包含返回const ref的问题。但是,按值返回温度是安全的。
j_random_hacker

请参阅C ++ Standard,12.2,第5段。另请参阅Herbutter.wordpress.com/2008/01/01/…,这是Herb Sutter的本周迷路大师。
David Thornley,2009年

4
@David:当函数的返回类型为“ T const&”时,实际发生的情况是return语句根据6.6.3.2 隐式地将类型为T的temp 转换为类型为“ T const&”(合法的转换但一个(不会延长生命周期),然后调用代码使用函数的结果(也就是“ T const&”类型)初始化“ T const&”类型的ref-同样,这是合法的但不会延长生命的过程。最终结果:没有延长使用寿命,并且造成了很多混乱。:(
j_random_hacker 2009年

26

我发现答案不令人满意,所以我加两分钱。

让我们分析以下情况:

错误使用

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

在实现Singleton模式时,这也很常见

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!

将引用返回给静态变量可能会导致不良行为,例如,考虑将trueIf((a*b) == (c*d))
乘员

Container::data()的实现应为return m_data;
Xeaz

这非常有帮助,谢谢!@Xeaz不会导致append调用出现问题吗?
安德鲁

@SebTu为什么还要这么做?
thorhunter

@Andrew不,这是一个语法shenanigan。例如,如果您返回了指针类型,则可以使用参考地址来创建和返回指针。
thorhunter

14

这不是邪恶的。像C ++中的许多事情一样,如果正确使用它会很好,但是在使用它时您应该注意很多陷阱(例如返回对局部变量的引用)。

用它可以实现很多事情(例如map [name] =“ hello world”)


1
我很好奇,有什么好处map[name] = "hello world"
错误的用户名,2011年

5
@wrongusername语法很直观。曾经尝试增加HashMap<String,Integer>Java中存储的值的计数吗?:P
Mehrdad Afshari

哈哈,还没有,但是看一下HashMap的例子,它看起来确实很粗糙:D
错误的用户名2011年

我遇到的问题:函数返回对容器中对象的引用,但是调用函数代码将其分配给局部变量。然后修改对象的某些属性。问题:容器中的原始对象保持不变。程序员如此轻松地忽略了&的返回值,然后您真的得到了意外的行为……
flohack 2014年

10

“返回引用是邪恶的,因为(据我了解),这很容易错过删除它”

不对。返回引用并不意味着所有权语义。也就是说,仅因为您这样做:

Value& v = thing->getTheValue();

...并不意味着您现在拥有v所引用的内存;

但是,这是可怕的代码:

int& getTheValue()
{
   return *new int;
}

如果您是因为“不需要在该实例上使用指针”而进行此类操作,则:1)如果需要引用,只需取消引用指针; 2)最终将需要使用指针,因为必须匹配删除,您需要一个指针来调用删除。


7

有两种情况:

  • const reference-好的主意,有时,尤其是对于重对象或代理类,编译器优化

  • 非常量引用-坏主意有时会破坏封装

两者都有相同的问题-可能指向被破坏的物体...

我建议在需要返回引用/指针的许多情况下使用智能指针。

另外,请注意以下几点:

有一个正式的规则-C ++标准(如果您感兴趣的话,请参见13.3.3.1.4节)指出临时只能绑定到const引用-如果您尝试使用非const引用,则编译器必须将其标记为一个错误。


1
非常量ref不一定会破坏封装。考虑vector :: operator []

这是一个非常特殊的情况……这就是为什么我有时会说的原因,尽管我真的应该声称大部分时间:)

因此,您是说正常的下标运算符实现是必要的邪恶吗?我既不同意也不同意。因为我都不是明智的。
尼克·博尔顿

我没有这么说,但是如果滥用:)))vector :: at可能会很邪恶……。–

嗯?vector :: at还返回一个非常量引用。

4

它不仅不邪恶,而且有时是必不可少的。例如,如果不使用参考返回值,就不可能实现std :: vector的[]运算符。


啊,当然可以;我认为这就是为什么我开始使用它的原因。当我第一次实现下标运算符[]时,我意识到引用的使用。我导致相信这是事实。
尼克·博尔顿

奇怪的是,您可以operator[]在不使用引用的情况下实现容器...并且std::vector<bool>可以。(并在此过程中造成了真正的混乱)
Ben Voigt

@BenVoigt mmm,为什么一团糟?对于没有直接映射到外部类型的复杂存储容器,返回代理也是一种有效的方案(就像::std::vector<bool>您提到的那样)。
Sergey.quixoticaxis.Ivanov

1
@ Sergey.quixoticaxis.Ivanov:混乱的是std::vector<T>,如果T可能的话,模板代码中的使用被破坏了bool,因为std::vector<bool>它的行为与其他实例不同。它很有用,但应该给它起自己的名字,而不是的特殊化std::vector
Ben Voigt

@BenVoight我同意关于使一个专业化“非常特别”的奇怪决定的观点,但是我认为您的原始评论暗示返回代理通常是奇怪的。
Sergey.quixoticaxis.Ivanov

2

除了接受的答案:

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!

1

返回引用通常用于大型对象的C ++运算符重载,因为返回值需要复制操作。(在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;
 }

这样,您可以安全地使用返回参考。

但是您总是可以使用指针而不是引用来返回functiong中的值。


0

我认为使用引用作为函数的返回值比使用指针作为函数的返回值要简单得多。其次,使用返回值所引用的静态变量始终是安全的。


0

最好的办法是创建对象,并将其作为引用/指针参数传递给分配此变量的函数。

在函数中分配对象并将其作为引用或指针返回(但是指针更安全)是个坏主意,因为在功能块末尾释放了内存。


-1
    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并在返回之前验证大小。


-2

应该从C ++中删除作为左值的函数(也就是返回非const引用)。这是非常不直观的。Scott Meyers希望此行为具有min()。

min(a,b) = 0;  // What???

这实际上不是对

setmin (a, b, 0);

后者甚至更有意义。

我认识到,作为左值的函数对于C ++样式流很重要,但值得指出的是C ++样式流很糟糕。我并不是唯一想到这一点的人。正如我记得Alexandrescu上有一篇有关如何做得更好的文章,我相信boost也试图创建一种更好的类型安全的I / O方法。


2
当然,这很危险,应该进行更好的编译器错误检查,但是如果没有它,就无法完成一些有用的构造,例如std :: map中的operator []()。
j_random_hacker

2
实际上,返回非常量引用非常有用。vector::operator[]例如。您是愿意写v.setAt(i, x)还是v[i] = x?后者优于FAR。
Miles Rout 2014年

1
@MilesRout我会去v.setAt(i, x)任何时候。是FAR上等。
刻苦

-2

我遇到了一个真正的问题,那就是它确实是邪恶的。本质上,开发人员返回了对矢量中对象的引用。太糟糕了!!!

我在一月份撰写的完整详细信息:http : //developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html


2
如果需要修改调用代码中的原始值,则需要返回引用。实际上,这与将迭代器返回到向量一样多,而且没有任何危险-如果将元素添加到向量中或从向量中移除,则两者都将无效。
j_random_hacker

该特殊问题是由于持有对vector元素的引用,然后以使引用无效的方式修改该vector引起的:Josuttis的“ C ++标准库:教程和参考”的第153页第6.2节内容为:“插入或删除元素会使引用以下元素的引用,指针和迭代器无效。如果插入导致重新分配,它将使所有引用,迭代器和指针无效”
Trent

-15

关于可怕的代码:

int& getTheValue()
{
   return *new int;
}

因此,实际上,内存指针在返回后丢失了。但是,如果您像这样使用shared_ptr:

int& getTheValue()
{
   std::shared_ptr<int> p(new int);
   return *p->get();
}

返回后内存不会丢失,分配后将被释放。


12
由于共享指针超出范围并释放整数,因此丢失了它。

指针没有丢失,引用的地址就是指针。
dgsomerton '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.