代码之家  ›  专栏  ›  技术社区  ›  davenpcj

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

  •  302
  • davenpcj  · 技术社区  · 6 年前

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

    根据对 this question I just asked, regarding initializing references ,返回引用可能是有害的,因为[据我所知]它会使您更容易错过删除引用,从而导致内存泄漏。

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

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

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

    16 回复  |  直到 6 年前
        1
  •  362
  •   GManNickG    8 年前

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

    如果你的意思是:

    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_ 因为调用我们的东西管理类实例的生命周期,所以 Ii 至少能活这么久。

    当然,只是:

    int getInt() {
       return 0;
    }
    

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

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

        2
  •  61
  •   Charlie Martin    16 年前

    不,不,不,一千次不。

    邪恶的是引用动态分配的对象并丢失原始指针。当你 new 你有义务得到担保的对象 delete .

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

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

    不起作用。

        3
  •  44
  •   travis    11 年前

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

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

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

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

        4
  •  14
  •   Mehrdad Afshari    16 年前

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

    有很多好的事情可以用它来实现(比如map[name]=“hello world”)。

        5
  •  14
  •   Scott    8 年前

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

    让我们分析以下情况:

    错误用法

    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!
    
        6
  •  10
  •   John Dibling    8 年前

    “返回参考是邪恶的,因为, 简单地说[据我所知]就是这样 更容易错过删除”

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

    Value& v = thing->getTheValue();
    

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

    但是,这是可怕的代码:

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

    如果你这样做是因为 "you don't require a pointer on that instance" 然后:1)如果需要引用,只需取消对指针的引用;2)最终需要指针,因为必须将新指针与删除匹配,并且需要一个指针来调用删除。

        7
  •  7
  •   Sasha    16 年前

    有两种情况:

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

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

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

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

    另外,请注意以下事项:

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

        8
  •  4
  •   anon    16 年前

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

        9
  •  1
  •   MinandLucy    11 年前

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

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

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

    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中,始终可以使用指针而不是引用返回值。

        10
  •  1
  •   AMA    8 年前

    接受答案的补充:

    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!
    
        11
  •  0
  •   Tony    11 年前

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

        12
  •  0
  •   Drezir    9 年前

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

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

        13
  •  -1
  •   Dan Olson    16 年前

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

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

    这不是真正的进步

    setmin (a, b, 0);
    

    后者甚至更有意义。

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

        14
  •  -1
  •   Amarbir    8 年前
        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并验证大小。

        15
  •  -2
  •   Adam Caviness    12 年前

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

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

        16
  •  -14
  •   Anatoly    12 年前

    关于恐怖代码:

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

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

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

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

    推荐文章