You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++类析构函数疑问:动态book数组仅触发一次析构的原因及影响

问题描述

我创建了一个book类,包含title、author等非动态分配的成员;随后创建了library类,其中包含动态book数组(book* set)、数组大小及若干方法。我在library类的析构函数中添加了delete操作符以释放set的内存(因为set是指针),同时在book类的析构函数中添加了输出语句,用于观察程序结束后所有book对象是否都会被销毁。

核心疑问

  1. 先创建3个book局部对象,添加到library后,运行代码仅看到library对象的析构输出和1次book的析构输出,而非预期的3次,这是为什么?
  2. 非动态对象的析构函数未被调用是否有影响?
  3. 知道动态对象的析构函数未被调用会导致内存泄漏,但当前情况会有什么问题?
  4. 局部对象和动态对象的析构函数机制通常是怎样的?

附上代码

class book
{
private:
    string title;
    string author;
public:
    book() { title = ""; author = ""; }
    book(string n, string a) : title(n), author(a) {}
    string getTitle() { return title; }
    string getAuthor() { return author; }

    book& operator=(const book& other)
    {
        title = other.title;
        author = other.author;
        return *this;
    }

    ~book() { cout << "Destructor of book \"" << title << "\" " <<  "was called!\n"; }
};

class library
{
private:
    book* set;
    int size;
    int ptr;
public:
    library()
    {
        size = 2;
        ptr = 0;
        set = new book[size];
    }

    void append(book &a)
    {
        if(ptr < size)
        {
            set[ptr] = a;
            ptr++;
        }
        else // 简单实现动态数组扩容,空间不足时创建2倍大小的新数组
        {
            book* copy = new book[size*2];
            for(int i = 0; i < size; i++)
                copy[i] = set[i];
            set = copy;
            set[ptr++] = a;
            size *= 2;
        }
    }

    ~library()
    {
        cout << "destructor of library was called.\n";
        delete set;
    }

};

int main()
{
    book one("Harry Potter", "J. K. Rowling");
    book two("The Little Prince", "Antoine de Saint-Exupery");
    book three("The Secret garden", "Frances Hodgson Bumett");
    library myLib;
    myLib.append(one);
    myLib.append(two);
    myLib.append(three);
    return 0;
}
解答

1. 为什么只看到1次book析构输出?

问题出在两个关键错误上:

  • 数组释放方式错误:用new book[size]创建动态数组时,必须用delete[] set释放,而非delete set。delete只会调用数组第一个元素的析构函数,再释放整个数组内存;delete[]才会遍历数组所有元素,依次调用每个book对象的析构函数。
  • 扩容时未释放旧数组:append方法扩容时,旧的set数组被直接丢弃,没有用delete[]释放,导致旧数组里的book对象永远不会触发析构,还会造成内存泄漏。

2. 非动态对象(局部book)的析构函数为什么没看到输出?

不是没调用,而是输出顺序的问题。局部对象one、two、three存储在栈上,main函数执行结束时,栈对象会按创建顺序的逆序销毁(先three,再two,最后one)。而library对象myLib也是栈对象,它的创建顺序在三个book之后,所以会先被销毁。修正delete为delete[]并补上旧数组的释放后,你会看到完整的输出:library析构输出 → 数组中4个book的析构输出 → 三个局部book的析构输出,总共7次。

3. 当前情况的问题

  • 多层内存泄漏:扩容时旧数组未释放,会导致book对象占用的堆内存泄漏;同时book的成员string内部也有动态内存,因为book析构未执行,string的析构也不会触发,进一步造成内存泄漏。
  • 未定义行为:用delete释放数组属于C++未定义行为,可能导致程序崩溃、内存损坏等不可预测的问题。

4. 局部对象和动态对象的析构机制

局部对象(栈对象)

  • 存储在栈内存中,当所在作用域(函数、代码块)结束时,自动按创建顺序的逆序销毁,编译器会自动调用其析构函数。
  • 无需手动管理生命周期,完全由编译器负责。

动态对象(堆对象)

  • 单个对象用new创建,数组用new[]创建,存储在堆内存中。
  • 单个对象必须用delete释放,触发一次析构;数组必须用delete[]释放,触发数组中每个元素的析构(顺序从第一个到最后一个)。
  • 必须手动释放,否则会造成内存泄漏,对象的析构逻辑永远不会执行(即使程序结束后操作系统回收堆内存,对象的析构代码也不会运行)。

修正后的关键代码

修改library的析构函数和扩容逻辑:

void append(book &a)
{
    if(ptr < size)
    {
        set[ptr] = a;
        ptr++;
    }
    else
    {
        book* copy = new book[size*2];
        for(int i = 0; i < size; i++)
            copy[i] = set[i];
        delete[] set; // 释放旧数组
        set = copy;
        set[ptr++] = a;
        size *= 2;
    }
}

~library()
{
    cout << "destructor of library was called.\n";
    delete[] set; // 用delete[]释放数组
}

内容的提问来源于stack exchange,提问作者comediann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 15:53:15