【问题标题】:In Which Situations To Delete A Pointer哪些情况下要删除指针
【发布时间】:2015-09-25 10:42:29
【问题描述】:

我的以下问题是关于内存管理的。例如,我有一个未在类中动态分配的 int 变量,比如说 invar1。我正在将此 int 的内存地址传递给另一个类构造函数。那个类是这样做的:

class ex1{
    ex1(int* p_intvar1)
    {
       ptoint = p_intvar1;
    }

    int* ptoint;
};

我应该删除 ptoint 吗?因为它有一个非动态分配的int的地址,我想我不需要删除它。

我再次使用 new 运算符向类声明一个对象:

objtoclass = new ex1();

然后我将它传递给另一个类:

class ex2{
    ex2(ex1* p_obj)
    {
       obj = p_obj;
    }

    ex1* obj;
};

当我已经在删除 objtoclass 时是否应该删除 obj?

谢谢!

【问题讨论】:

  • constructor 应该是ex1 的构造函数吗?
  • 使用 std::shared_ptr 并忘记内存管理。
  • delete 你是什么new(和delete[] 你是什么new[]),但只有一次。
  • 由于objobjtoclass 指向同一个对象,您可以使用其中任何一个来删除该对象。请注意,这两个指针都将指向垃圾。
  • 大声笑反对什么。伙计们,这是一个难得的好问题示例。

标签: c++


【解决方案1】:

因为它有一个非动态分配的 int 的地址,我想我不需要删除它。

正确。

当我已经在删除 objtoclass 时是否应该删除 obj?

没有。

回想一下,您实际上并没有删除指针;您正在使用指针来删除它们指向的东西。因此,如果您同时编写了delete objdelete objtoclass,因为两个指针都指向同一个对象,您将删除该对象两次。

我会提醒你,这是你的ex2 类很容易犯的错误,其中指向对象的所有权语义并不完全清楚。您可以考虑使用智能指针实现来消除风险。

【讨论】:

    【解决方案2】:

    只是其他答案的附录

    借助智能指针(shared_ptrunique_ptr),您可以摆脱原始指针并忘记内存管理。

    智能指针负责在超出范围时释放内存。

    这是一个例子:

    #include <iostream>
    #include <memory>
    
    class ex1{
    public:
        ex1(std::shared_ptr<int> p_intvar1)
        {
            ptoint = p_intvar1;
            std::cout << __func__ << std::endl;
        }
        
        ~ex1()
        {
            std::cout << __func__ << std::endl;
        }
    private:
        std::shared_ptr<int> ptoint;
    };
    
    int main()
    {
        std::shared_ptr<int> pi(new int(42));
        std::shared_ptr<ex1> objtoclass(new ex1(pi));
    
        /* 
         * when the main function returns, these smart pointers will go
         * go out of scope and delete the dynamically allocated memory
         */ 
    
        return 0;
    }
    

    输出:

    ex1
    ~ex1
    

    【讨论】:

    • 好建议,好榜样,但你没有回答问题。
    • @LightnessRacesinOrbit 我同意。但它仍然可能对 OP 有所帮助。你认为它应该被删除吗?
    • 不要使用auto_ptr
    • @SaZ 你是对的,谢谢。自 C++11 起已弃用。我已经编辑了答案。
    【解决方案3】:

    当我已经在删除 objtoclass 时是否应该删除 obj?

    您可以,但请注意,两次删除同一个对象是未定义的行为,应该避免。例如,如果您有两个指针指向同一个对象,并且您使用一个指针删除原始对象,则可能会发生这种情况 - 那么您也不应该使用另一个指针删除该内存。在您的情况下,您可能会得到两个指向同一个对象的指针。

    一般来说,构建一个在内部管理内存的类(就像您看起来那样)并非易事,您必须考虑诸如 rule of three 之类的事情。

    关于应该删除动态分配的内存,你是对的。如果内存不是动态分配的,则不应删除它。

    PS。为了避免上述并发症,您可以使用智能指针。

    【讨论】:

      【解决方案4】:

      您当前没有删除这个 int,也没有显示它的分配位置。如果两个对象都不应该拥有它的参数,我会写

      struct ex1 {
          ex1(int &i_) : i(i_) {}
          int &i;               // reference implies no ownership
      };
      struct ex2 {
          ex2(ex1 &e_) : e(e_) {}
          ex1 &e;               // reference implies no ownership
      };
      
      int i = 42;
      ex1 a(i);
      ex2 b(a);
      

      如果任一参数应该由新对象拥有,则将其作为unique_ptr 传递。如果任一参数应该是共享,请使用shared_ptr。与原始指针相比,我通常更喜欢这些(引用或智能指针)中的任何一个,因为它们提供了有关您意图的更多信息。


      一般来说,要做出这些决定,

      我应该删除 ptoint 吗?

      是错误的问题。首先考虑稍高一点的事情:

      1. 这个 int 在您的程序中代表什么?
      2. 谁拥有它?
      3. 与使用它的这些类相比,它应该存在多长时间?

      然后看看这些例子的答案是如何自然得出的:

      • 这个 int 是一个 I/O 映射控制寄存器。

        在这种情况下,它不是用new 创建的(它存在于你的整个程序之外),因此你当然不应该删除它。它可能也应该标记为volatile,但这不会影响生命周期。

        也许你的类之外的东西映射了地址,也应该取消映射它,这大致类似于(取消)分配它,或者它可能只是一个众所周知的地址。

      • 此 int 是全局日志记录级别。

        在这种情况下,它可能具有静态生命周期,在这种情况下,没有人拥有它,它没有被显式分配,因此不应该被显式取消分配

        或者,它由一个记录器对象/singleton/mock/whatever拥有,并且该对象负责在必要时释放它

      • 这个 int 被明确地赋予你的对象来拥有

        在这种情况下,最好让这一点显而易见,例如。

        ex1::ex1(std::unique_ptr<int> &&p) : m_p(std::move(p)) {}
        

        请注意,将您的本地数据成员设置为 unique_ptr 或类似的,也会自动处理生命周期,而无需您付出任何努力。

      • 这个 int 被赋予您的对象以供使用,但其他对象也可能正在使用它,而且它们将以何种顺序完成并不明显。

        使用shared_ptr&lt;int&gt; 而不是unique_ptr 来描述这种关系。同样,智能指针将为您管理生命周期。

      一般来说,如果你可以在 type 中编码所有权和生命周期信息,你就不需要记住在哪里手动分配和解除分配。这样更清晰、更安全。

      如果您不能在类型中对该信息进行编码,那么您至少可以清楚自己的意图:您询问解除分配而没有提及生命周期或所有权这一事实表明您正在工作在错误的抽象级别上。

      【讨论】:

        【解决方案5】:

        因为它有一个非动态分配的 int 的地址,所以我 我以为我不需要删除它。

        没错。干脆不要删除它。

        您问题的第二部分是关于动态分配的内存。在这里,您必须多考虑并做出一些决定。

        假设您的名为 ex1 的类在其构造函数中接收到一个原始指针,用于在类外部动态分配的内存。

        作为类的设计者,你必须决定这个构造函数是否“取得这个指针的所有权”。如果是,则 ex1 负责删除其内存,您可能应该在类析构函数上执行此操作:

        class ex1 {
        public:
            /**
             * Warning: This constructor takes the ownership of p_intvar1,
             * which means you must not delete it somewhere else.
             */
            ex1(int* p_intvar1)
            {
                ptoint = p_intvar1;
            }
        
            ~ex1()
            {
                delete ptoint;
            }
        
            int* ptoint;
        };
        

        但是,这通常是一个糟糕的设计决定。您必须为此类的用户 root 阅读构造函数的注释,并记住不要删除在类 ex1 之外的某处分配的内存。

        接收指针并取得其所有权的方法(或构造函数)称为“sink”。

        有人会像这样使用这个类:

        int* myInteger = new int(1);
        ex1 obj(myInteger); // sink: obj takes the ownership of myInteger
        // never delete myInteger outside ex1
        

        另一种方法是说您的类 ex1 不获取所有权,并且为该指针分配内存的人负责删除它。 ex1 类不能删除其析构函数上的任何内容,应该这样使用:

        int* myInteger = new int(1);
        ex1 obj(myInteger);
        // use obj here
        delete myInteger; // remeber to delete myInteger
        

        同样,您班级的用户必须阅读一些文档才能知道他负责删除这些内容。

        如果您不使用现代 C++,则必须在这两个设计决策之间做出选择。

        在现代 C++(C++ 11 和 14)中,您可以在代码中明确说明(即不必仅依赖代码文档)。

        首先,在现代 C++ 中,您避免使用原始指针。您必须在两种“智能指针”之间进行选择:unique_ptr 或 shared_ptr。它们之间的区别在于所有权。

        正如他们的名字所说,一个唯一指针只属于一个人,而共享指针可以由一个或多个人拥有(所有权是共享的)。

        唯一指针 (std::unique_ptr) 不能被复制,只能从一个地方“移动”到另一个地方。如果一个类有一个唯一的指针作为属性,那么这个类就明确地拥有那个指针的所有权。如果一个方法接收到一个唯一的指针作为副本,那么它就明确表示它是一个“接收器”方法(获取指针的所有权)。

        你的类 ex1 可以这样写:

        class ex1 {
        public:
            ex1(std::unique_ptr<int> p_intvar1)
            {
                ptoint = std::move(p_intvar1);
            }
        
            std::unique_ptr<int> ptoint;
        };
        

        这个类的用户应该像这样使用它:

        auto myInteger = std::make_unique<int>(1);
        ex1 obj(std::move(myInteger)); // sink
        // here, myInteger is nullptr (it was moved to ex1 constructor)
        

        如果您忘记在上面的代码中执行“std::move”,编译器将生成一个错误,告诉您 unique_ptr 不可复制。

        还请注意,您永远不必明确删除内存。智能指针会为您处理。

        【讨论】:

          猜你喜欢
          • 2011-01-23
          • 1970-01-01
          • 2012-11-14
          • 1970-01-01
          • 2011-08-29
          • 1970-01-01
          • 2016-02-19
          • 2020-08-09
          • 1970-01-01
          相关资源
          最近更新 更多