递归链表:所有对象均调用析构函数问题咨询
Alright, let's figure out why you're seeing all those extra destructor calls and fix this recursive linked list properly.
First, Let's Spot the Core Issues
Looking at your code, there are a few critical problems causing the excessive destructor invocations:
Type Mismatch & Invalid Initialization
Yourcuris declared asint, but you're trying to assignnullptrto it in the constructor — that's a type error (nullptris a pointer, not an integer). Plus, you're usingnew intto assign tocur, which confirms you actually intendedcurto be anint*(pointer to int), not a rawint.Shallow Copy Disaster
Yourconsfunction returns*thisby value, which triggers the default copy constructor. The default copy does a shallow copy: it copies the pointer values ofcurandnxtinstead of creating new copies of the data they point to. This means multipleListobjects end up sharing the same memory addresses. When each of these copies is destroyed, they all try to free the same memory, leading to repeated destructor calls (and likely crashes from double-free errors).Unimplemented Memory Cleanup
Your destructor only prints a message — it doesn't free the dynamically allocatedcuror recursively delete thenxtnode. This would cause memory leaks even if the destructor wasn't being called extra times.
Fixed Implementation
Here's a corrected version of your code with explanations of key changes:
#include <iostream> #include <utility> // For std::move class List { int* cur; // Fixed: changed to int* to match your dynamic allocation logic List* nxt; public: // Default constructor: initialize pointers to nullptr with initializer list List() : cur(nullptr), nxt(nullptr) {} // Constructor for a single node with a value explicit List(int val) : cur(new int(val)), nxt(nullptr) {} // Destructor: properly free dynamic memory and recursively delete next nodes ~List() { delete cur; // Free the int stored in this node delete nxt; // Recursively destroy the next node std::cout << "Destroyed\n"; } // Disable default copy constructor/assignment to prevent shallow copies List(const List&) = delete; List& operator=(const List&) = delete; // Move constructor: transfer ownership of resources instead of copying List(List&& other) noexcept : cur(other.cur), nxt(other.nxt) { // Null out the original object's pointers so it doesn't free the resources we took other.cur = nullptr; other.nxt = nullptr; } // Move assignment operator List& operator=(List&& other) noexcept { if (this != &other) { // Free current resources first delete cur; delete nxt; // Transfer ownership from other to this cur = other.cur; nxt = other.nxt; // Null out other's pointers other.cur = nullptr; other.nxt = nullptr; } return *this; } // Fixed cons: return a reference instead of a value to avoid copying List& cons(const int& a) { if (cur == nullptr) { cur = new int(a); } else { // Create a new node to hold the current value List* new_node = new List; new_node->cur = cur; new_node->nxt = nxt; // Update current node to hold the new value cur = new int(a); nxt = new_node; } return *this; // Return reference to avoid copy } // Friend declaration for ostream operator friend std::ostream& operator<<(std::ostream& os, const List& list); }; // Implement the ostream operator to print the list std::ostream& operator<<(std::ostream& os, const List& list) { const List* current = &list; while (current != nullptr && current->cur != nullptr) { os << *(current->cur); if (current->nxt != nullptr && current->nxt->cur != nullptr) { os << " -> "; } current = current->nxt; } return os; } // Test the implementation int main() { List my_list; my_list.cons(3).cons(2).cons(1); std::cout << my_list << "\n"; // Output: 1 -> 2 -> 3 return 0; }
Key Fixes Explained
- Return References Instead of Values: The
consfunction now returnsList&instead ofList, so no copy of theListobject is created when you chain calls likecons(3).cons(2). - Disable Shallow Copies: We deleted the default copy constructor and assignment operator to prevent accidental shallow copies that cause double-free errors.
- Proper Destructor: The destructor now recursively frees all nodes and the
intvalues they hold, ensuring no memory leaks and only one destructor call per node. - Move Semantics: Added move constructor/assignment to allow efficient transfer of
Listobjects without copying, which is useful if you need to pass or return lists elsewhere.
Simplified Alternative (No Dynamic Int)
If you don't need to dynamically allocate the int (which is probably unnecessary here), you can simplify the code even more by making cur a raw int and using std::optional to track empty states:
#include <iostream> #include <optional> #include <utility> class List { std::optional<int> cur; List* nxt; public: List() : cur(std::nullopt), nxt(nullptr) {} explicit List(int val) : cur(val), nxt(nullptr) {} ~List() { delete nxt; std::cout << "Destroyed\n"; } List(const List&) = delete; List& operator=(const List&) = delete; List(List&& other) noexcept : cur(std::move(other.cur)), nxt(other.nxt) { other.nxt = nullptr; } List& operator=(List&& other) noexcept { if (this != &other) { delete nxt; cur = std::move(other.cur); nxt = other.nxt; other.nxt = nullptr; } return *this; } List& cons(const int& a) { if (!cur.has_value()) { cur = a; } else { List* new_node = new List(*cur); new_node->nxt = nxt; cur = a; nxt = new_node; } return *this; } friend std::ostream& operator<<(std::ostream& os, const List& list); }; std::ostream& operator<<(std::ostream& os, const List& list) { const List* current = &list; while (current != nullptr && current->cur.has_value()) { os << current->cur.value(); if (current->nxt != nullptr && current->nxt->cur.has_value()) { os << " -> "; } current = current->nxt; } return os; }
This version eliminates the need to manage dynamic int allocations, reducing the chance of errors.
内容的提问来源于stack exchange,提问作者Arii

