【问题标题】:C++ pointers and memory leakC++ 指针和内存泄漏
【发布时间】:2015-12-15 15:59:27
【问题描述】:

我想说明一件我看到的事情:

class Something {};

void do_something(const Something *p_sth) {}

然后:

do_something(new Something());

应该会导致内存泄漏,因为当您调用 new 时,您也应该始终调用 delete,对吗?这会是一个好的解决方案吗?

void do_something(const Something *p_sth)
{
    delete p_sth;
}

还是使用引用&更好?我还发现智能指针可以解决这个问题,所以不需要 delete (这似乎是一件好事,但我以前从未使用过它)。我只想知道最好的解决方案是什么,以避免内存泄漏。谢谢

*谢谢大家的回答。它帮助我清理了一些事情。我也很抱歉我发布的代码可能过于笼统。

【问题讨论】:

  • “应该导致内存泄漏......”如果不知道 do_something 对其参数做了什么,就无法判断。换句话说,除非函数文档说明你应该这样做,否则不要new 参数。
  • 您已经提到了许多可能的解决方案。 '正确'的取决于特定的应用程序(你想在哪里使用它?它会与继承一起使用吗?等等......)
  • 使用智能指针更好,但是是的,你写的不会导致内存泄漏,因为函数会清理它。您只能在无法获取 nullptr 作为值时使用引用...所以这取决于情况
  • 附注:如果你使用智能指针,你不应该调用也不应该新建或删除,你可以使用 make_unique 和 make_shared
  • 在现代 C++ 中,您实际上需要指针的情况很少(当您不能使用引用时的多态性就是其中之一)。 C++11 中引入的新智能指针应该更多地从资源所有权的角度来看待,而不是简单的自动删除指针。

标签: c++ pointers memory-leaks


【解决方案1】:

您关于假设指针所有权并删除对象的建议有问题。

这种行为在 C++ 中不是惯用的。程序员希望他们必须删除使用new 分配的每个对象。如果您的函数的用户希望他们负责删除将其地址传递给函数的对象,那么您的解决方案就会分崩离析。此外,它还可以防止将您的函数与在调用结束后必须保持存在的对象一起使用:

Something* s = new s();
do_something(s);
s.foo() // oops, object was deleted, I can't use it anymore
delete s; // oops, double delete because I'm for some reason not responsible for deleting the object that I allocated

您的解决方案还可以防止在函数中使用自动和静态分配的对象。

Something s;
do_something(&s); //oops, do_something assumes that object is dynamically allocated

所有这些警告都必须记录给函数的用户。

在函数内部没有删除的原始指针没有这些问题。管理调用者的内存真的不应该是你的函数的责任。这样做会破坏单一责任原则。当您不想想要转让或共享所有权时,原始指针参数没有任何问题。如果您确实想暗示所有权的更改,请使用专为此设计的智能指针。

智能指针不存在上述一些问题,但它们仍然会阻止将函数与自动和静态对象一起使用。

在许多情况下,参考参数是理想的。它告诉调用者他们仍然对该对象负责。作为奖励,不需要 addressof 运算符允许稍微更好的语法。当然,调用者可能仍然忘记管理他们的记忆,但正如我指出的,这不应该是你的责任。

引用有一个潜在的缺点。它们不能为空。如果你不需要null,那么它实际上是一个优势。

哪种解决方案是理想的,取决于您的需要。以下并非适用于所有极端情况,但适用于大多数常见情况:

  • 如果要修改对象,则传递引用。
    • 除非你需要 null,在这种情况下使用指针
  • 如果你只想读取对象,那么
    • 如果对象是可复制的、小(大小小于或等于 word)、不包含指向动态对象的指针且不是多态的,则按值传递
    • 否则,或者如果您因为正在编写模板而不知道这些事情,请传递一个 const 引用
    • 除非你需要 null,在这种情况下使用指针
  • 如果要转让所有权,请使用unique_ptr
  • 如果您希望共享该所有权,请使用shared_ptr

【讨论】:

    【解决方案2】:

    最好使用智能指针

    class Something {};
    
    void do_something(std::shared_ptr<Something> p_sth) 
    {
    ...
    }
    

    这样一来,当您查看原型时所有权就很清楚,并且在您离开范围时会自动删除。

    【讨论】:

      【解决方案3】:

      我只想知道避免内存泄漏的最佳解决方案是什么。谢谢

      确保没有内存泄漏的最佳解决方案是使用std::shared_ptr,它是一个智能指针,一旦它没有任何引用,就会自行删除。如果您对同一指针有多个引用,则此指针是最好的。如果在给定时间只有一个引用,请使用 std::unique_ptr。

      这将防止内存泄漏,我也更喜欢使用智能指针而不是标准指针,因为它们非常强大。另外,保留问题:

      还是使用引用&更好?

      在需要引用对象的任何地方使用引用,如果删除引用,作为智能指针,它也会删除指针(没有其他对该指针的引用)

      我希望这能回答你的问题:)

      【讨论】:

        【解决方案4】:

        最好避免使用原始指针。

        如果您不需要堆分配并且可以使用堆栈分配的变量,那么更喜欢通过引用传递(如果有适当的移动构造函数,甚至可以通过值传递,但这是另一天的话题)。

        如果您需要堆分配(用于多态性、依赖注入、信息隐藏、所有权转移等),请确定该对象的所有权语义,并使用适当的智能指针来管理这些语义。

        如果所有其他方法都失败并且您必须使用原始指针(也许您正在处理遗留接口或 C 接口或类似的东西),那么再次明确定义您的所有权语义并记录它们以明确谁负责用于删除堆分配对象等

        如果您必须有一个指向堆栈分配对象的原始指针,请记录您不会删除该指针,并注意记录指针的生命周期以避免在对象超出范围后访问该对象。

        【讨论】:

          【解决方案5】:

          最干净的解决方案是完全避免使用指针。 是的,参考是个好主意。

          void do_something(const Something &p_sth) {}
          

          然后你可以在堆栈上声明你的“东西”。

          void function_that_does_something()
          {
              Something mySomething;
          
              do_something( mySomething );
          }
          

          最好让编译器为你做清理工作。

          【讨论】:

            猜你喜欢
            • 2013-10-31
            • 1970-01-01
            • 1970-01-01
            • 2019-06-18
            • 2014-09-25
            • 2014-08-25
            • 1970-01-01
            • 1970-01-01
            • 2016-05-14
            相关资源
            最近更新 更多