繁体   English   中英

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

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

我觉得这有点主观; 我不确定这个意见是否会一致(我已经看过很多代码片段,其中返回了引用)。

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

这让我很担心,因为我跟随了一些例子(除非我想象事情)并且在相当多的地方做过这样的事情......我误解了吗? 这是邪恶的吗? 如果是这样,那有多邪恶?

我觉得因为我的指针和引用混合在一起,再加上我是C ++的新手,以及对什么时候使用的完全混淆,我的应用程序必须是内存泄漏地狱......

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

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

如果你的意思是:

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

如果生命周期应该留给调用者,而你只是计算值。

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

不,不,一千次没有。

什么是邪恶的是引用动态分配的对象并丢失原始指针。 当您new对象时,您有义务保证delete

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

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

不行。

您应该返回对不会立即消失的现有对象的引用,以及您不打算进行任何所有权转移的对象。

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

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

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

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

我们来分析下列情况:

错误的使用

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!

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

用它可以实现好东西(比如map [name] =“hello world”)

“返回引用是邪恶的,因为,根据我的理解,它更容易错过删除它”

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

Value& v = thing->getTheValue();

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

但是,这是一个可怕的代码:

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

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

有两种情况:

  • const引用 - 很好的想法,有时候,特别是对于重型对象或代理类,编译器优化

  • 非常规引用 - 有时会破坏封装

两者都有相同的问题 - 可能潜在地指向被破坏的对象......

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

另请注意以下事项:

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

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

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

除了接受的答案:

 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!

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

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

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

    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函数可以在删除后访问动态内存甚至是null对象。 这可能导致错误访问异常。 相反,应该在返回之前实现getter和setter并进行大小验证。

应该从C ++中删除作为左值的函数(也就是返回非const引用)。 这非常不直观。 Scott Meyers想要一个min()这种行为。

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

这并不是一个真正的改进

setmin (a, b, 0);

后者甚至更有意义。

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

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

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

关于可怕的代码:

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM