380

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

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

这让我很担心,因为我已经遵循了一些例子(除非我在想象事情)并且在很多地方都这样做了......我误解了吗?是邪恶的吗?如果是这样,到底有多邪恶?

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

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

4

16 回答 16

460

一般来说,返回一个引用是完全正常的,并且一直在发生。

如果你的意思是:

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

如果生命周期应该由调用者决定,而您只是在计算值。

摘要:如果对象的生命周期在调用后不会结束,则可以返回引用。

于 2009-04-15T16:58:31.873 回答
66

不,不,不,一千次不。

邪恶的是引用动态分配的对象并丢失原始指针。当你new一个对象你承担了一个有保证的义务delete

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

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

不会工作。

于 2009-04-15T16:52:34.180 回答
58

您应该返回对不会立即消失的现有对象的引用,并且您不打算在其中进行任何所有权转让。

永远不要返回对局部变量或类似变量的引用,因为它不会被引用。

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

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

于 2009-04-15T19:39:29.100 回答
37

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

让我们分析以下案例:

错误使用

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!
于 2016-04-22T12:53:55.000 回答
14

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

用它可以实现很多好处(比如 map[name] = "hello world")

于 2009-04-15T16:51:23.153 回答
11

“返回参考是邪恶的,因为 [据我所知] 很容易错过删除它”

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

Value& v = thing->getTheValue();

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

然而,这是一个可怕的代码:

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

如果您正在做这样的事情是因为“您不需要该实例上的指针”,那么:1)如果您需要引用,只需取消引用指针,2)您最终将需要指针,因为您必须匹配new 带有一个删除,你需要一个指针来调用删除。

于 2009-04-15T19:28:54.150 回答
7

有两种情况:

  • const reference——好主意,有时,特别是对于重对象或代理类,编译器优化

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

两者都有相同的问题——可能指向被破坏的对象......

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

另外,请注意以下事项:

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

于 2009-04-15T16:59:00.030 回答
4

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

于 2009-04-15T16:55:47.263 回答
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!
于 2017-07-21T22:35:32.850 回答
1

返回引用通常用于C++中大对象的运算符重载,因为返回值需要复制操作。(在运算符重载中,我们通常不使用指针作为返回值)

但是返回引用可能会导致内存分配问题。因为对结果的引用会作为对返回值的引用传出函数,所以返回值不能是自动变量。

如果你想使用返回引用,你可以使用静态对象的缓冲区。例如

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

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

但是你总是可以在函数中使用指针而不是引用来返回值。

于 2013-11-20T18:55:47.467 回答
0

我认为使用引用作为函数的返回值比使用指针作为函数的返回值更直接。其次,使用返回值所引用的静态变量总是安全的。

于 2014-07-29T02:35:35.503 回答
0

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

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

于 2015-10-05T07:51:37.167 回答
-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 并验证大小。

于 2017-03-14T01:46:56.917 回答
-2

应该从 C++ 中删除作为左值的函数(又名,返回非常量引用)。这非常不直观。Scott Meyers 想要一个具有这种行为的 min() 。

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

这并不是真正的改进

setmin (a, b, 0);

后者甚至更有意义。

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

于 2009-04-15T19:52:47.680 回答
-3

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

我在一月写的全部细节:http: //developer-resource.blogspot.com/2009/01/pros-and-cons-of-reuring-references.html

于 2009-04-15T20:53:40.483 回答
-16

关于可怕的代码:

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

所以,确实,内存指针在返回后丢失了。但是如果你像这样使用 shared_ptr :

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

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

于 2013-07-23T05:59:38.793 回答