类成员和显式堆/栈分配

41

假设我们有以下4个类:

class A
{
    public:           
        A(void) : m_B()
        {
        }
    private:
        B m_B;
}

class B
{
    public:            
        B(void)
        {
           m_i = 1;
        }
    private:
        int m_i;
}

class C
{
    public:           
        C(void) 
        {
            m_D = new D();
        }
        ~C(void) 
        {
            delete m_D;
        }
    private:
        D *m_D;
}

class D
{
    public:           
        D(void)
        {
           m_i = 1;
        }
    private:
        int m_i;
}

假设有4种情况:

情况1:A在堆栈上外部分配,B在堆栈上内部分配

A myA1;

情况2:A在堆上外部分配,B在栈上内部分配。

A *myA2 = new A();

情况3:C 在栈上外部分配,D 在堆上内部分配。

C myC1;

第4种情况:C在堆上外部分配,D在堆上内部分配

C *myC2 = new C();
在每种情况下发生了什么?例如,在情况2中,我理解指针myA2在堆栈上分配,A对象存在于堆中,但是m_B属性呢?我认为它也在堆上分配了空间,因为对象存在于堆空间中,然后其属性超出作用域是没有意义的。如果这是正确的,那么外部堆分配会覆盖内部堆栈分配吗?
在情况3中,myC1在堆栈上分配,然而m_D在堆上分配。这里会发生什么?这两个部分是否跨越内存分开?如果我从析构函数中删除'delete m_D',并且myC1超出范围,那么分配给m_D的堆空间是否会有内存泄漏?
如果有任何详细介绍此内容的教程/文章,我非常希望得到一个链接。
3个回答

64

我认为你混淆了"堆栈分配"和"自动变量"。

自动变量在离开上下文时会自动销毁。

堆栈分配是指内存在执行堆栈上分配。在堆栈上分配的变量是自动变量。

此外,成员变量是自动变量,当其所有者被销毁时会调用它们的析构函数。对于指针,它们会被销毁,但底层对象不会被销毁,您必须显式调用delete。要确保底层对象被销毁,您必须使用智能或唯一指针。

换句话说:您必须调用delete的变量/成员变量不是自动变量。

最后,类的成员在同一内存段上分配给其所有者。

在您的代码中:

  • A.m_B是一个自动变量。如果A在堆栈上,B也在堆栈上;如果A在堆上,B也在堆上。
  • B.m_iD.m_i是自动变量,并将分配在其所有者的相同内存段上
  • 指针C.m_D是一个自动变量,但类型为D的指向对象不是自动变量,您必须显式调用delete删除底层对象。因此,指针C.m_D分配在相同的内存段上,但底层对象不是。它明显由new分配,并且将位于堆上。

所以:

  • 情况1:所有内容都在堆栈上并且是自动的(即:自动销毁)。
  • 情况二:myA2 在堆上而非自动变量(需要使用delete myA2)。其成员m_B2是一个自动变量,当myA2被销毁时,它也会被销毁。另外,由于myA2在堆上,m_B与类的任何成员一样,在同一内存空间中即堆上。
  • 情况三:myC1 是自动变量,位于栈上。指向m_D的指针也在栈上,但所指对象m_D是通过new在堆上分配的。
  • 情况四:与情况三相同,但myC2位于堆上,并非自动变量。因此,您需要删除myC2(这将导致m_D也被删除)。

  • “为了确保底层对象被销毁,您必须使用智能指针或独占指针。” -> “您必须使用”是一个误导性的结论,因为智能指针只是为了方便而存在;它们绝对不是强制性的。无论如何,回答很好 :) - Markus

    9

    案例 1:所有内容都在“堆栈”上(自动存储)。当您退出作用域时,资源将被释放。

    案例 2: myA2 存在于“堆”上,它的 m_B 也是如此,因此您只需担心释放 myA2 占用的资源。当 myA2 被销毁时,它的 m_B 也会自动销毁。

    案例 3: myC1 存在于堆栈上,它的 m_D 指向堆上的一个 D,但是 C 析构函数负责删除它,因此当 myC1 超出其作用域时,所有动态分配的资源都会被清除。

    案例 4: myC2 是动态分配的,因此必须删除它以释放占用的资源。删除它将调用其构造函数,该构造函数进而处理其 m_D,就像案例 3 中一样。

    我不确定有多少关于文章的内容,但我建议阅读一些好的C++书籍


    2

    你的对象是一块有序的内存。对象不会在堆栈上分配其成员,而只是由其成员组成。

    情况2:整个对象存在于堆中,这意味着它的所有成员都位于堆中。

    情况3:整个对象存在于堆栈中。关键在于,作为myC1成员的不是D类实例,而是指向B的指针物理上成为myC1的成员。因此,myC1的成员位于堆栈上,并指向位于堆中的某个D实例。


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