C++类析构函数疑问:动态book数组仅触发一次析构的原因及影响
问题描述
我创建了一个book类,包含title、author等非动态分配的成员;随后创建了library类,其中包含动态book数组(book* set)、数组大小及若干方法。我在library类的析构函数中添加了delete操作符以释放set的内存(因为set是指针),同时在book类的析构函数中添加了输出语句,用于观察程序结束后所有book对象是否都会被销毁。
核心疑问
- 先创建3个
book局部对象,添加到library后,运行代码仅看到library对象的析构输出和1次book的析构输出,而非预期的3次,这是为什么? - 非动态对象的析构函数未被调用是否有影响?
- 知道动态对象的析构函数未被调用会导致内存泄漏,但当前情况会有什么问题?
- 局部对象和动态对象的析构函数机制通常是怎样的?
附上代码
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
相关产品推荐
相关产品推荐

