【问题标题】:In c++, is passing an on-the-fly dynamically allocated object to a function (always) a bad idea?在 c++ 中,将动态动态分配的对象传递给函数(总是)是个坏主意吗?
【发布时间】:2012-10-25 21:16:31
【问题描述】:

我知道这个问题的标题看起来有点伤脑筋,但我真的不知道如何用一句话来问这个问题。我会告诉你我的意思:

void f(T *obj)
{
    // bla bla
}
void main()
{
    f(new T());
}

据我所知,(几乎)每个 new 都需要一个 delete,这需要一个指针(由 new 返回)。在这种情况下,new 返回的指针不会存储在任何地方。那么这会是内存泄漏吗?

C++ 是否有某种魔法(程序员不可见)在函数结束后删除对象,还是这种做法总是一个坏主意?

【问题讨论】:

  • 函数结束后肯定不会被删除。它可以在函数结束时删除,但在这种情况下,它本质上是一个局部变量,不需要堆分配。
  • 除非f() 删除它,否则它会泄漏。
  • @FredLarson 但这不是一个好的编程习惯,对吧...
  • @connectionist - 这基本上就是智能指针构造函数的工作方式 - 你 new() 一些东西,并将其传递给构造函数。然后智能指针负责删除。但我同意,总的来说,这是一种代码味道。

标签: c++ function pointers


【解决方案1】:

没有特别的魔法,不会自动调用delete。

这绝对不是“总是一个坏主意”——如果函数以某种形式取得对象的所有权,那么它是调用此类函数的完全有效的方式:

container.AddAndTakeOwnership(new MyItem(42));

【讨论】:

  • @conectionist,是的。但是 ecatmur 的答案更好,具有“坏主意”的不同含义(我的是“代码不是完全错误的”,ecatmur 的答案是“代码是安全的并且可以自我记录”)
【解决方案2】:

是的,这总是一个坏主意;函数和构造函数应该明确它们的所有权语义,如果它们希望获取或共享传递的指针的所有权,它们应该分别收到 std::unique_ptrstd::shared_ptr

即使在标准库中,也有许多采用具有所有权语义的原始指针的旧 API(例如,locale 构造函数采用具有所有权的 Facet *),但任何新代码都应避免这种情况。

即使在构造unique_ptrshared_ptr 时,您也可以避免使用new 关键字,在后一种情况下使用make_shared,在前一种情况下编写make_unique 函数模板,其中添加should fairly soon语言。

【讨论】:

    【解决方案3】:

    在这种情况下,new 返回的指针不会存储在任何地方。所以 这会是内存泄漏吗?

    不,不一定是内存泄漏。指针存储为f 的参数:

    void f(T *obj)
    //        ^^^  here pointer is "stored"
    {
        // bla bla
        delete obj; // no memory leak if delete will be called on obj
    }
    void main()
    {
        f(new T());
      //  ^^^^^^^  this "value" will be stored as an argument to f
    }
    

    C++ 是否具有某种魔力(程序员看不到) 在函数结束后删除对象或者只是这种做法 总是个坏主意?

    在你的例子中没有魔法。正如我所展示的 - 必须明确调用删除。

    更好的是使用智能指针,然后C++“魔术”起作用,并且不需要删除。

    void f(std::unique_ptr<T> obj)
    {
        // bla bla
    }
    void main()
    {
        f(new T());
    }
    

    【讨论】:

      【解决方案4】:

      显示的代码将导致内存泄漏。 C++ 没有垃圾收集,除非您明确使用专门的框架来提供它。

      原因与 C/C++ 中管理内存的方式有关。对于局部变量,例如您的示例,直接从操作系统(malloc)请求对象的内存,然后指向对象的指针存在于堆栈中。因为 C/C++ 可以做任意复杂的指针运算,编译器无法知道对象的某处是否存在其他指针,所以函数 f() 结束时无法回收内存。

      为了自动防止泄漏,必须从托管堆中分配内存,并且必须仔细跟踪对该堆的每个引用,以确定何时不再使用给定对象。为了获得这种能力,你必须放弃 C 的指针算术能力。

      例如,假设编译器可以神奇地发现对 obj 的所有正常引用都已失效并删除了该对象(释放了内存)。如果你有一些非常复杂的 RUNTIME DEPENDENT 表达式,比如 void* ptr = (&&&&(&&&*obj)/2++ - currenttime() - 567 + 3^ 2 % 52) 等;编译器如何知道这个 ptr 是否指向 obj?没有办法知道。这就是没有垃圾收集的原因。您可以进行垃圾收集或复杂的运行时指针运算,不能同时进行。

      【讨论】:

      • C++11 为 可追踪指针对象 (3.7.4.3 [basic.stc.dynamic.safety]) 定义语义以便工作正是这种情况。诚然,执行严格指针安全的实现仍然相当少见。
      • 或者您可以使用现代 C++ 功能为您管理内存 std::shared_ptr&lt;T&gt;boost::ptr_vector&lt;T&gt; 等。以上大部分内容与现代 C++ 完全无关。
      【解决方案5】:

      这通常在您的函数 f() 将对象存储在某个地方时使用,例如数组(或任何其他数据容器)或简单的类成员;在这种情况下,删除将(并且必须)在其他地方进行。

      否则不是一个好主意,因为无论如何您都必须在函数结束时手动删除它。在这种情况下,您可以声明一个自动(在堆栈上)对象并通过指针传递它。

      【讨论】:

        【解决方案6】:

        没有魔法。在您的情况下, f 被称为 main 后返回到 CRT 的 main ,最终操作系统将清理“泄漏”。这不一定是一个坏主意,它可能会赋予 f 所有权,并且由 f 来做事并最终删除。有些人称之为不好的做法,但它可能在野外。

        编辑: 尽管我认为该代码并不比以下代码更危险:

        void f(T *obj)
        {
            // bla bla
        }
        void main()
        {
            T* test = new T ();
            f(test);
        }
        

        基本上是一样的。它对 f 说,这是一个指向某个内存的指针,它是你的,你现在照顾它。

        【讨论】:

        • 身处野外并不意味着这是一种不错的做法。唯一的理由是传递所有权,在这种情况下,现代代码库应该使用unique_ptr,它提供了所描述的确切行为。在较旧的代码库(C++11 之前)中,必须记录所有权的概念,以帮助避免双重释放内存。
        • 我知道它是...我见过这个(在 QRegExpValidator 示例中,如果我没记错的话,在书中),它看起来很可疑...
        • @pickypg 我不建议这样做。只是说某人,在某个地方会有一个理由。他们总是这样做,这并不总是一个坏理由。
        【解决方案7】:

        在 C++ 中不鼓励传递指针,因为没有关联的所有者语义(即,您无法知道谁拥有该指针,因此谁负责删除该指针)。因此,当您确实有一个函数(需要一个指针)时,您需要非常清楚地记录该函数是否负责清理指针。当然,像这样的文档很容易出错,因为用户必须阅读它。

        在 C++ 程序中传递描述事物所有权语义的对象更为正常。

        1. 通过引用传递
          该函数没有取得对象的所有权。它只会使用对象。
        2. 传递一个 std::auto_ptr(或 std::unique_ptr)
          函数正在传递指针和指针的所有权。
        3. 传递一个 std::shared_ptr
          该函数正在传递指针的共享所有权。

        通过使用这些技术,您不仅可以记录所有权语义,而且使用的对象还将自动控制对象的生命周期(从而使您的函数免于调用 delete)。

        因此,在现代 C++ 代码中手动调用 delete 实际上非常罕见。

        所以我会这样写:

        void f(std::unique_ptr<T> obj)
        {
            // bla bla
        }
        int main()
        {
            f(std::unique_ptr<T>(new T()));
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-29
          • 1970-01-01
          • 2018-04-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多