返回C ++引用变量的做法是邪恶的吗?

Is the practice of returning a C++ reference variable evil?

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

根据我刚才对这个问题的一个评论,关于初始化引用,返回引用可能是有害的,因为[据我所知]它使得错过删除引用更容易,这可能导致内存泄漏。

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

我觉得,由于我的混合指针和引用包,加上我是C++的新事实,以及在使用什么时候完全混淆,我的应用程序必须是内存泄漏地狱…

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


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

如果你的意思是:

1
2
3
4
int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}

那是各种各样的邪恶。分配给i的堆栈将消失,而您所指的是无内容。这也是邪恶的:

1
2
3
4
int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

因为现在客户最终不得不做一些奇怪的事情:

1
2
3
4
5
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

注意,右值引用仍然只是引用,所以所有邪恶的应用程序都保持不变。

如果要分配超出函数范围的内容,请使用智能指针(或通常使用容器):

1
2
3
std::unique_ptr<int> getInt() {
    return std::make_unique<int>(0);
}

现在客户端存储一个智能指针:

1
std::unique_ptr<int> x = getInt();

引用也可以访问那些你知道生命周期在更高层次上保持开放的事物,例如:

1
2
3
4
5
6
7
struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

在这里,我们知道返回对i_的引用是可以的,因为无论调用什么,我们都管理类实例的生命周期,所以i_至少会活那么长时间。

当然,只是:

1
2
3
int getInt() {
   return 0;
}

如果生存期应该留给调用者,而您只是在计算值。

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


不,不,不,一千次。

邪恶是指对一个动态分配对象的引用和丢失原始指针。当你使用EDOCX1,0个对象时,你就有义务保证一个EDCOX1×1。

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

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

不起作用。


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

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

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

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


我觉得答案不令人满意,所以我将加上我的两分钱。

让我们来分析以下情况:

错误用法

1
2
3
4
5
int& getInt()
{
    int x = 4;
    return x;
}

这显然是错误的。

1
int& x = getInt(); // will refer to garbage

静态变量用法

1
2
3
4
5
int& getInt()
{
   static int x = 4;
   return x;
}

这是正确的,因为静态变量在程序的整个生命周期中都是存在的。

1
int& x = getInt(); // valid reference, x = 4

这在实现单例模式时也是很常见的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class Singleton
{
    public:
        static Singleton& instance()
        {
            static Singleton instance;
            return instance;
        };

        void printHello()
        {
             printf("Hello");
        };

}

用途:

1
2
 Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
 my_sing.printHello();  //"Hello"

算子

例如,标准库容器很大程度上依赖于返回引用的运算符的使用。

1
T & operator*();

可用于以下情况

1
2
3
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

快速访问内部数据

有时可以使用&;快速访问内部数据

1
2
3
4
5
6
7
8
9
10
11
Class Container
{
    private:
        std::vector<int> m_data;

    public:
        std::vector<int>& data()
        {
             return m_data;
        }
}

用法:

1
2
3
Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1

然而,这可能会导致这样的陷阱:

1
2
3
4
5
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!


这不是邪恶的。与C++中的很多东西一样,如果使用正确,这是很好的,但是在使用它时有很多陷阱(比如返回一个局部变量的引用)。

有好的东西可以用它来实现(比如地图[名字] ="Hello World")


"returning a reference is evil because,
simply [as I understand] it makes it
easier to miss deleting it"

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

1
Value& v = thing->getTheValue();

……并不意味着你现在拥有V所指的记忆;

但是,这是可怕的代码:

1
2
3
4
int& getTheValue()
{
   return *new int;
}

如果您这样做是因为"您不需要该实例上的指针",那么:1)如果需要引用,只需取消对指针的引用;2)您最终将需要指针,因为您必须将一个新的与一个删除匹配,并且需要一个指针来调用Delete。


有两种情况:

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

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

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

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

另外,请注意以下事项:

有一个正式的规则——C++标准(第133.3.1.4节,如果您感兴趣的话)声明临时只能绑定到const引用——如果您尝试使用非const引用,编译器必须将此标记为错误。


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


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

但返回引用可能导致内存分配问题。由于对结果的引用将作为对返回值的引用从函数中传递出去,因此返回值不能是自动变量。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
 }

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

但在函数g中,始终可以使用指针而不是引用返回值。


接受答案的补充:

1
2
3
4
5
6
7
struct immutableint {
    immutableint(int i) : i_(i) {}

    const int&amp; get() const { return i_; }
private:
    int i_;
};

我认为这个例子是不好的,如果可能的话应该避免。为什么?最后很容易得到一个悬空的参考。

举例说明这一点:

1
2
3
4
5
6
7
8
9
10
struct Foo
{
    Foo(int i = 42) : boo_(i) {}
    immutableint boo()
    {
        return boo_;
    }  
private:
    immutableint boo_;
};

进入危险区:

1
2
Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!

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

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


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


函数作为LValk(AKA,非const引用返回)应该从C++中删除。这是非常不合理的。ScottMeyers想要一个Min()来处理这个行为。

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

这不是真正的进步

1
setmin (a, b, 0);

后者甚至更有意义。

我认识到,LValk函数对于C++风格的流很重要,但值得指出的是C++风格的流是可怕的。我不是唯一一个这么想的人…我记得Alexandrescu有一篇关于如何做得更好的大文章,我相信Boost也试图创建更好的类型安全I/O方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    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并验证大小。


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

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


关于恐怖代码:

1
2
3
4
int& getTheValue()
{
   return *new int;
}

所以,实际上,返回后内存指针丢失了。但是如果你使用这样的共享资源:

1
2
3
4
5
int& getTheValue()
{
   std::shared_ptr<int> p(new int);
   return *p->get();
}

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