C++基于范围的for循环致对象销毁(含重复销毁)问题排查
栈上Book对象析构异常的原因分析与解决
问题现象
- 栈上创建的book1、book2等对象看似被立即销毁
- 同一书名的析构函数被连续调用2-3次,调用菜单case3的
showBooks函数时该现象重复出现
相关代码
#include <iostream> #include <vector> using namespace std; class Book { public: // Constructor Prototype Book(string author = "Auth", string title = "Title", string publisher = "Pub", float price = 1.99, int stock = 1); // Destructor Prototype ~Book(); // Methods string get_title() const { return title; } private: string author; string title; string publisher; float price; int stock; }; // Constructor definition Book::Book(string author, string title, string publisher, float price, int stock) : author{author}, title{title}, publisher{publisher}, price{price}, stock{stock} { cout << "Book: " << title << " was created." << endl; } // Destructor definition Book::~Book() { cout << "Book: " << title << " has been destroyed." << endl; } // Functions void showMenu(); void getChoice(); void userChoice(int choice); void showBooks(vector<Book> bookList); // Global Vector vector<Book> bookList; int main() { // Creating Book objects for testing purposes Book book1("Marcel Proust", "In Search of Lost Time", "Pub", 14.99, 5); Book book2("James Joyce", "Ulysses", "Pub", 25.99, 4); Book book3("Miguel de Cervantes", "Don Quixote", "Pub", 35.99, 3); Book book4("Gabriel Garcia Marquez", "One Hundred Years of Solitude", "Pub", 100.99, 2); Book book5("F. Scott Fitzgerald", "The Great Gatsby", "Pub", 49.99, 1); // Pushing book1-5 into the vector of Book objects bookList.push_back(book1); bookList.push_back(book2); bookList.push_back(book3); bookList.push_back(book4); bookList.push_back(book5); while(true) { showMenu(); getChoice(); } return 0; } void showMenu() { cout << "\tMENU" << endl; cout << "1. Purchase Book" << endl; cout << "2. Search for Book" << endl; cout << "3. Show Books available" << endl; cout << "4. Add new Book" << endl; cout << "5. Edit details of Book" << endl; cout << "6. Exit" << endl; } void getChoice() { int choice; cout << "Enter your choice (1-6): "; cin >> choice; userChoice(choice); } void userChoice(int choice) { switch(choice) { case 1: cout << "Case 1 called." << endl; break; case 2: cout << "Case 2 called." << endl; break; case 3: cout << "Case 3 called." << endl; showBooks(bookList); break; case 4: cout << "Case 4 called." << endl; break; case 5: cout << "Case 5 called." << endl; break; case 6: cout << "Case 6 called." << endl; exit(0); } } void showBooks(vector<Book> bookList) { for (Book bk : bookList) { cout << "Book: " << bk.get_title() << endl; } }
运行输出示例
Book: In Search of Lost Time was created. Book: Ulysses was created. Book: Don Quixote was created. Book: One Hundred Years of Solitude was created. Book: The Great Gatsby was created. Book: In Search of Lost Time has been destroyed. Book: In Search of Lost Time has been destroyed. Book: Ulysses has been destroyed. Book: In Search of Lost Time has been destroyed.
原因分析
vector扩容引发的拷贝对象销毁
vector默认会在容量不足时自动扩容:每次push_back如果当前容量不够,会重新分配一块更大的内存,把现有元素拷贝到新内存,然后销毁旧内存里的元素。你看到的多次同一书名析构,就是旧内存里的拷贝对象被销毁导致的。而栈上的book1-book5并没有被立即销毁,它们的生命周期会持续到main函数结束。传值调用导致的额外拷贝与销毁
showBooks函数的参数是vector<Book> bookList,这是传值传递,会把全局的bookList完整拷贝一份,函数执行完毕后,这个拷贝的vector会被销毁,里面的所有Book对象都会触发析构。- for循环中的
Book bk : bookList也是值拷贝,每个循环都会创建一个bk的临时拷贝,循环结束后该临时对象被销毁,又会多一次析构调用。
解决方案
提前给vector预留容量
在push_back之前调用bookList.reserve(5),让vector一开始就分配能容纳5个元素的空间,避免后续扩容时的拷贝与销毁:int main() { bookList.reserve(5); // 提前预留容量 Book book1(...); // ... 其他book对象创建 bookList.push_back(book1); // ... 其他push_back操作 }使用引用传递避免拷贝
修改showBooks函数的参数为const引用,同时循环中也使用const引用遍历:void showBooks(const vector<Book>& bookList) // 引用传递 { for (const Book& bk : bookList) // 引用遍历 { cout << "Book: " << bk.get_title() << endl; } }直接在vector中构造对象(可选)
使用emplace_back代替push_back,直接在vector的内存空间中构造Book对象,不需要先在栈上创建再拷贝:int main() { bookList.reserve(5); bookList.emplace_back("Marcel Proust", "In Search of Lost Time", "Pub", 14.99, 5); bookList.emplace_back("James Joyce", "Ulysses", "Pub", 25.99, 4); // ... 其他书籍直接构造 // 不再需要栈上的book1-book5对象 }
内容的提问来源于stack exchange,提问作者Chavon4
相关产品推荐
相关产品推荐

