【问题标题】:Deleting a reference删除参考
【发布时间】:2010-07-13 03:29:02
【问题描述】:

这有效吗?可接受的做法?

typedef vector<int> intArray;

intArray& createArray()
{
    intArray *arr = new intArray(10000, 0);

    return(*arr);
}


int main(int argc, char *argv[])
{

    intArray& array = createArray();

    //..........

    delete &array;

    return 0;
}

【问题讨论】:

  • 有效吗?是的,可以接受吗?天哪,不!为什么?为什么会有人这样做?我需要知道!
  • @gnzlbg 大概是因为这“确保”了返回值不能null(尽管参见Billy ONeal关于异常安全的观点)。
  • 如果向量必须在堆上:使用自动处理释放的智能指针。另请注意,返回值不能为 null,因为正在使用的 new 版本要么工作要么抛出。无论如何,这是非常糟糕的 API 设计,必须通过(非拥有)引用来释放内存并不是常见的做法。而且由于实际上空引用确实有效,您会相信编写此 API 的人不会返回空引用吗?我不会。

标签: c++


【解决方案1】:

代码的行为将是您的预期行为。现在,问题在于,虽然您可能认为编程是为编译器编写一些东西来处理,但它同样是关于编写其他程序员(或将来的您)将理解并能够维护的东西。您提供的代码在很多情况下相当于使用了编译器的指针,但对于其他程序员来说,它只是一个潜在的错误来源。

引用旨在成为在其他地方管理的对象的别名,以其他方式管理。通常人们在遇到delete &amp;ref 时会感到惊讶,并且在大多数情况下,程序员不会期望必须在引用地址上执行delete,因此将来很有可能有人会调用该函数忘记删除,您将有内存泄漏。

在大多数情况下,使用智能指针可以更好地管理内存(如果您不能使用其他高级构造,例如std::vectors)。通过将指针隐藏在引用后面,您将更难在返回的引用上使用智能指针,因此您无济于事,反而使用户更难使用您的界面。

最后,关于引用的好处在于,当您在代码中阅读它们时,您知道对象的生命周期是在其他地方管理的,您不必担心它。通过使用引用而不是指针,您基本上会回到单一解决方案(以前在 C 中只有指针),并且突然必须特别注意所有引用,以确定是否必须在那里管理内存。这意味着更多的努力,更多的时间来考虑内存管理,更少的时间去担心正在解决的实际问题——由于异常代码的额外压力,人们习惯于用指针寻找内存泄漏,并期望没有引用.

简而言之:通过引用保存内存对用户隐藏了处理内存的要求,并使其更难正确处理。

【讨论】:

    【解决方案2】:

    是的,我认为它会起作用。但是,如果我在我处理的任何代码中看到类似的内容,我会立即将其撕掉并重构。

    如果您打算返回分配的对象,请使用指针。 请!

    【讨论】:

    • 可以说,他们可能希望将其包装在适当的智能指针中,以使其异常安全。
    • @Steven Sudit,合适的智能指针是auto_ptr: stackoverflow.com/questions/631633/…
    • 是的,这当然是合理的默认值。我只是不想过于具体,因为我并不了解所有非 ANSI 智能指针,例如 Boost 中的智能指针。
    • @Steven Sudit,我的做法一直是返回原始指针,因为它具有最大的灵活性。但是我链接到的那个问题的答案表明 auto_ptr 具有相同的灵活性,即使考虑到 Boost 中的选择。它是标准的一部分这一事实使其成为理所当然的事情。
    • 当然,现在值得一提的是,auto_ptr 已被弃用,取而代之的是其优越的替代品,unique_ptrshared_ptr,视情况而定。
    【解决方案3】:

    它是有效的......但我不明白你为什么想要这样做。这不是异常安全的,std::vector 无论如何都会为你管理内存。为什么new呢?

    编辑:如果你从一个函数返回新的内存,你应该返回指针,以免你的函数的用户爆炸。

    【讨论】:

    • 这不是异常安全的吗?调用者就是main()函数吧?
    • @Tanz:在 main() 的特定情况下,您是“安全的”,因为您知道任何未在 main 中处理的异常都将被未处理(导致 termninate() 在出现异常的情况下被抛出)。但是(1)您仍然应该使用对象来管理资源,即使是在 main 中,如果没有别的,为了保持一致性,以及(2)OP 的问题是一般性的,而不是特定于 main()。
    【解决方案4】:

    这有效吗?

    是的。

    可接受的做法?

    没有。

    这段代码有几个问题:

    • 设计最不令人惊讶的行为的准则被打破:您返回的东西“看起来像”一个对象但必须被客户端代码删除(这应该意味着一个指针 - 一个引用应该是总是指向的东西到一个有效的对象)。

    • 您的分配可能会失败。即使你在分配函数中检查结果,你会返回什么?无效的参考?对于这种情况,您是否依赖分配引发异常?

    作为设计原则,请考虑创建一个负责管理对象生命周期的 RAII 对象(在本例中为智能指针),或者在创建它的同一抽象级别删除指针:

    typedef vector<int> intArray;
    
    intArray& createArray()
    {
        intArray *arr = new intArray(10000, 0);
    
        return(*arr);
    }
    
    void deleteArray(intArray& object)
    {
        delete &object;
    }
    
    int main(int argc, char *argv[])
    {
        intArray& array = createArray();
        //..........
        deleteArray(array);
        return 0;
    }
    

    这种设计提高了编码风格的一致性(分配和解除分配被隐藏并在相同的抽象级别实现)但通过指针而不是引用仍然更有意义(除非您的对象是动态分配的事实必须出于某种设计原因保留实现细节)。

    【讨论】:

    • 为什么分配失败是个问题?如果分配失败,该函数返回一个异常(那个版本的 operator new 抛出)。
    【解决方案5】:

    它会起作用,但恐怕这是完全不可接受的做法。在 C++ 世界中有一个很强的约定,即内存管理是使用指针完成的。你的代码违反了这个约定,并且可能会绊倒几乎所有使用它的人。

    您似乎正在竭尽全力避免从此函数返回原始指针。如果您担心必须反复检查 main 中的有效指针,您可以使用引用来处理您的数组。但是让 createArray 返回一个指针,并确保删除数组的代码也将它作为指针。或者,如果真的这么简单,只需在 main 中的堆栈上声明数组并完全放弃该函数。 (这种情况下的初始化代码可以引用要初始化的数组对象,调用者可以将其堆栈对象传递给初始化代码。)

    【讨论】:

      【解决方案6】:

      是有效的,因为编译器可以编译运行成功。然而,这种编码实践使得代码对于读者和维护者来说更加困难,因为

      • 手动内存管理
      • 模糊的所有权转移到客户端

      但是这个问题有一个微妙的地方,那就是效率要求。有时我们无法返回传递值,因为对象大小可能太大,如本例所示笨重 (1000 * sizeof(int));是因为;如果我们需要将对象传输到代码的不同部分,我们应该使用指针。但这并不意味着上面的实现是可以接受的,因为对于这种需求,有一个非常有用的工具,它是smart-pointers。因此,设计决定取决于程序员,但对于这种特定的实现细节,程序员应该使用可接受的模式,如本例中的智能指针。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-21
        • 1970-01-01
        相关资源
        最近更新 更多