在哪些情况下需要删除指针?

14

我接下来的问题是内存管理。例如,我在一个类中有一个未动态分配的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?

谢谢!


2
constructor应该是ex1的构造函数吗? - Codor
1
使用std::shared_ptr,忘记内存管理。 - Jepessen
4
删除你所 new 的对象(以及删除你所 new[] 的数组),但只需执行一次。 - Some programmer dude
1
由于objobjtoclass指向同一个对象,您可以使用任何一个来删除该对象。请注意,这时两个指针都将指向垃圾。 - David Schwartz
4
笑死,怎么会被踩呢?这是一个很好的问题,伙计们。 - Lightness Races in Orbit
显示剩余2条评论
5个回答

12

由于它具有未动态分配的int的地址,我认为我不需要删除它。

正确。

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

不需要。

请记住,您实际上没有删除指针;您使用指针来删除它们所指向的东西。因此,如果您同时编写delete objdelete objtoclass,因为两个指针都指向同一个对象,您将删除该对象两次。

我要提醒您,在您的ex2类中,该指向的对象的所有权语义并不是完全清楚的,您可能需要考虑使用智能指针实现来减少风险。


8

其他答案的补充说明

使用智能指针(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

1
好的建議和很好的例子,但你沒有回答問題。 - Lightness Races in Orbit
@LightnessRacesinOrbit 我同意。但对楼主仍然可能有帮助。您认为它应该被删除吗? - sergej
不要使用 auto_ptr - Dmitry Sazonov
@SaZ 你是对的,谢谢。自C++11以来它已经被弃用了。我已经编辑了答案。 - sergej

4

我在删除objtoclass时,是否应该删除obj?

你可以这样做,但要注意,多次删除同一对象是未定义的行为,应该避免。例如,如果你有两个指针指向同一个对象,并使用其中一个指针删除原始对象,则不应使用另一个指针也删除该内存。在你的情况下,你可能会最终得到两个指向同一对象的指针。

通常,构建一个管理内存的类(像你现在做的那样)并不容易,你必须考虑到诸如rule of three等因素。

关于动态分配内存应该删除的问题,你是正确的。如果内存没有动态分配,就不应该删除它。

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


2

您目前并未删除此 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. 相对于使用它的这些类,它的寿命有多长?
然后看看答案如何自然地适用于这些示例。
  • this int is an I/O mapped control register.

    In this case it wasn't created with new (it exists outside your whole program), and therefore you certainly shouldn't delete it. It should probably also be marked volatile, but that doesn't affect lifetime.

    Maybe something outside your class mapped the address and should also unmap it, which is loosely analogous to (de)allocating it, or maybe it's simply a well-known address.

  • this int is a global logging level.

    In this case it presumably has either static lifetime, in which case no-one owns it, it was not explicitly allocated and therefore should not be explicitly de-allocated

    or, it's owned by a logger object/singleton/mock/whatever, and that object is responsible for deallocating it if necessary

  • this int is being explicitly given to your object to own

    In this case, it's good practice to make that obvious, eg.

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

    Note that making your local data member a unique_ptr or similar, also takes care of the lifetime automatically with no effort on your part.

  • this int is being given to your object to use, but other objects may also be using it, and it isn't obvious which order they will finish in.

    Use a shared_ptr<int> instead of unique_ptr to describe this relationship. Again, the smart pointer will manage the lifetime for you.

通常情况下,如果你能在类型中编码所有权和生命周期信息,那么你就不需要记住手动分配和释放的位置,这样更加清晰和安全。

如果你不能在类型中编码这些信息,至少要明确你的意图:没有提到生命周期或所有权而问及释放操作,说明你正在错误的抽象层次上工作。


2
因为它具有未动态分配的 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;
};

然而,这通常是一个糟糕的设计决策。你必须支持使用这个类的用户读取构造函数上的注释,并记住不要删除在类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 ++(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不可复制。此外,请注意您永远不必显式删除内存。智能指针会为您处理这些事情。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接