如何在C++中使用链表实现正确的拷贝构造函数?
如何正确实现链表栈的拷贝构造函数?
你给出的拷贝构造函数写法确实有问题——这是典型的浅拷贝,会导致两个Stack对象共享同一份链表内存,带来严重的内存安全问题。我来给你拆解问题,再一步步写出正确的深拷贝实现。
为什么原写法错误?
// 错误的拷贝构造函数 Stack::Stack(const Stack& obj) { head = obj.head; }
这段代码只是把当前对象的head指针直接指向了传入对象obj的链表头节点。这意味着两个Stack对象会共享同一份链表内存:当其中一个对象被销毁时,它的析构函数会释放整个链表,另一个对象的head就变成了野指针,后续访问或销毁这个对象都会触发未定义行为(比如程序崩溃)。
要解决这个问题,我们需要实现深拷贝——为新对象创建一份完全独立的链表副本,而不是共享原对象的内存。
正确的拷贝构造函数实现
Stack::Stack(const Stack& obj) { head = nullptr; // 初始化当前对象的链表为空 Node* current = obj.head; // 用于遍历原链表的指针 Node** tail = &head; // 指向当前链表尾部指针的指针,方便添加新节点 // 遍历原链表,逐个复制节点 while (current != nullptr) { // 创建新节点,复制原节点的item值 *tail = new Node(current->item); // 将tail移动到新节点的next指针地址,准备下一次添加 tail = &((*tail)->next); // 移动原链表的遍历指针 current = current->next; } }
代码逻辑解释:
- 先把当前对象的
head初始化为nullptr,确保链表一开始是空的。 - 用
current指针遍历传入对象obj的整个链表。 - 用
tail指针跟踪当前新链表的尾部:每次创建一个新节点(复制原节点的item),把它链接到新链表的尾部,然后更新tail的位置,直到遍历完原链表的所有节点。
这样新对象就拥有了完全独立的链表,和原对象互不干扰。
额外建议:遵循三/五法则
实现了拷贝构造函数后,你还应该实现赋值运算符重载和析构函数(遵循C++的三法则/五法则),避免其他场景下的浅拷贝问题。这里提供两种常见的赋值运算符实现方式:
方式1:手动实现
Stack& Stack::operator=(const Stack& obj) { if (this != &obj) { // 防止自赋值导致的内存泄漏 // 先销毁当前对象的原有链表 Node* temp = head; while (head != nullptr) { head = head->next; delete temp; temp = head; } // 执行和拷贝构造函数相同的深拷贝逻辑 head = nullptr; Node* current = obj.head; Node** tail = &head; while (current != nullptr) { *tail = new Node(current->item); tail = &((*tail)->next); current = current->next; } } return *this; }
方式2:拷贝交换 idiom(更简洁、异常安全)
这种方式利用拷贝构造函数和交换函数来实现,代码更简洁且具备异常安全性:
// 先实现一个交换函数 void Stack::swap(Stack& other) noexcept { std::swap(head, other.head); } // 赋值运算符:传值调用会先触发拷贝构造,再交换 Stack& Stack::operator=(Stack other) { swap(other); return *this; }
当赋值时,other是传入对象的拷贝,交换后当前对象拥有新的链表,而other会在函数结束时销毁,带走原来的旧链表,自动完成内存释放。
内容的提问来源于stack exchange,提问作者swagalistic
相关产品推荐
相关产品推荐

