在哪些情况下应该删除指针

In Which Situations To Delete A Pointer

本文关键字:删除 指针 情况下      更新时间:2023-10-16

我下面的问题是关于内存管理的。例如,我有一个int变量,不是在类中动态分配的,我们设为invar1。我把这个的内存地址传递给另一个类的构造函数。这个类的作用是:

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

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

我又用new操作符声明了一个类的对象:

objtoclass = new ex1();

我把它传递给另一个类:

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

我应该删除obj时,我已经删除objtoclass?

谢谢!

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

正确的。

我应该删除obj时,我已经删除objtoclass?

记住,你实际上并不是在删除指针;你用指针来删除它们指向的东西。因此,如果您同时写入delete objdelete objtoclass,因为两个指针都指向同一个对象,您将删除该对象两次。

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

只是其他答案的附录

在智能指针(shared_ptr, unique_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

我应该删除obj时,我已经删除objtoclass?

好吧,你可以,但要注意,删除同一个对象两次是未定义的行为,应该避免。这可能会发生,例如,如果你有两个指针指向同一个对象,你用一个指针删除原始对象,那么你不应该用另一个指针删除内存。在你的情况下,你最终可能会有两个指针指向同一个对象。

一般来说,构建一个在内部管理内存的类(就像你表面上做的那样)并不是微不足道的,你必须考虑到像三规则之类的事情。

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

p。为了避免上述的复杂情况,你可以使用智能指针。

当前没有删除这个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。我通常更喜欢这些(引用或智能指针)而不是原始指针,因为它们提供了更多关于您意图的信息。


一般来说,为了做出这些决定,

我应该删除点吗?

是错误的问题。首先考虑稍微高一些的层次:

  1. 这个int在程序中表示什么?
  2. 谁拥有它,如果有人的话?
  3. 与使用它的类相比,它应该存活多长时间?

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

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

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

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

  • this int是一个全局日志级别。

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

    或者,它由一个日志对象/singleton/mock/等等拥有,并且该对象负责在必要时释放它

  • this int被显式地给定给你的对象来拥有

    在这种情况下,让它变得明显是一个好习惯,例如:

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

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

  • 这个int是给你的对象使用,但其他对象也可能使用它,这是不明显的顺序,他们将完成

    shared_ptr<int>代替unique_ptr来描述这个关系。同样,智能指针将为您管理生命周期。

一般来说,如果您可以在类型中编码所有权和生命周期信息,您就不需要记住在哪里手动分配和释放东西。这样更清晰,更安全。

如果您不能在类型中编码该信息,您至少可以清楚地表明您的意图:事实上,您询问了重新分配而没有提及生命周期或所有权,这表明您在错误的抽象级别上工作。

因为它有一个未动态分配的整型对象的地址,所以I我想我不需要删除它。

正确。请不要删除。

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

假设你的类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和c++ 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是不可复制的。

还要注意,您永远不必显式地删除内存。