为何自定义NTree类的Copy Constructor无法实现拷贝功能?
new未生效的问题分析与解决 嘿,我一眼就瞅出你代码里的核心问题了:你虽然用new分配了新内存,但紧接着就把这些新指针直接覆盖成原对象的指针了,相当于new的操作完全白做,最终实现的是浅拷贝,而非你想要的深拷贝,这就是为什么调试时看到内存地址没变化。
咱们一步步拆解你的代码问题:
你的拷贝构造函数里的错误点
先看你写的拷贝构造函数:
NTree(const NTree& aOtherNTree) { fKey = new string; // 这里确实分配了新的string内存 fNodes[N] = new NTree<string, N>; // 这里还犯了数组越界的低级错误! fKey = aOtherNTree.fKey; // 直接把fKey指向原对象的指针,刚new的内存直接丢了 for (int i = 0; i < N; i++) { fNodes[i] = aOtherNTree.fNodes[i]; // 子树指针直接复制,还是共享内存 } }
指针覆盖导致内存泄漏+浅拷贝
你先用new string给fKey分配了新内存,但下一行fKey = aOtherNTree.fKey;直接把fKey指向了原对象的fKey地址——刚分配的内存没人管了(内存泄漏),而且新对象和原对象共享同一个fKey内存,这就是你看到地址没变的原因。数组越界的未定义行为
fNodes[N] = new NTree<string, N>;这里,fNodes是大小为N的数组,合法索引范围是0到N-1,访问fNodes[N]属于越界操作,会触发未定义行为(可能崩溃、数据乱掉)。子树浅拷贝导致悬空指针
循环里直接复制fNodes[i]的指针,意味着新对象的子树和原对象指向同一块内存。当其中一个对象析构时,会释放这些子树内存,另一个对象再访问就会出现悬空指针,甚至触发double free错误。
修正后的深拷贝实现
要实现真正的深拷贝,你需要把原对象的值拷贝到新分配的内存里,而不是直接复制指针。同时要递归处理子树,还要注意哨兵节点NIL的特殊情况:
修正后的拷贝构造函数
NTree(const NTree& aOtherNTree) { // 处理原对象是空树(哨兵)的情况 if (aOtherNTree.isEmpty()) { fKey = nullptr; for (int i = 0; i < N; i++) { fNodes[i] = &NIL; } return; } // 深拷贝fKey:分配新内存,把原对象的key值拷贝进去 fKey = new T(*aOtherNTree.fKey); // 深拷贝每个子树 for (int i = 0; i < N; i++) { const NTree<T, N>& child = *aOtherNTree.fNodes[i]; if (child.isEmpty()) { // 原节点是哨兵,直接指向全局NIL fNodes[i] = &NIL; } else { // 递归调用拷贝构造函数,创建新的子树实例 fNodes[i] = new NTree<T, N>(child); } } }
修正后的赋值运算符重载
你的赋值运算符也有同样的问题,而且需要注意自我赋值检查、先释放旧内存再分配新内存:
NTree& operator=(const NTree& aOtherNTree) { // 自我赋值检查,避免不必要的操作和内存错误 if (this == &aOtherNTree) { return *this; } // 先释放当前对象的旧内存 if (!isEmpty()) { // 释放fKey delete fKey; // 释放每个非哨兵的子树 for (int i = 0; i < N; i++) { if (fNodes[i] != &NIL) { delete fNodes[i]; } } } // 然后执行深拷贝,逻辑和拷贝构造函数一致 if (aOtherNTree.isEmpty()) { fKey = nullptr; for (int i = 0; i < N; i++) { fNodes[i] = &NIL; } return *this; } fKey = new T(*aOtherNTree.fKey); for (int i = 0; i < N; i++) { const NTree<T, N>& child = *aOtherNTree.fNodes[i]; if (child.isEmpty()) { fNodes[i] = &NIL; } else { fNodes[i] = new NTree<T, N>(child); } } return *this; }
额外需要修正的其他问题
普通构造函数的悬空指针问题
你在NTree(const T& aKey)里写了fKey = &aKey;,这会让fKey指向传入参数的内存地址,如果传入的是局部变量,当局部变量销毁后,fKey就会变成悬空指针。正确做法是:NTree(const T& aKey) { fKey = new T(aKey); // 分配新内存并拷贝值 for (int i = 0; i < N; i++) fNodes[i] = &NIL; }析构函数的逻辑错误
当前的析构函数没有正确释放fKey的内存,而且判断逻辑有问题,修正后:~NTree() { // 哨兵节点不需要释放任何内存 if (isEmpty()) { return; } // 释放fKey delete fKey; // 释放每个非哨兵的子树 for (int i = 0; i < N; i++) { if (fNodes[i] != &NIL) { delete fNodes[i]; } } }
总结
你之前的问题本质是混淆了指针赋值和值拷贝:new只是帮你分配了一块空内存,但你没有把原对象的内容拷贝进去,反而直接让新指针指向原对象的内存,自然实现不了深拷贝。只有把原对象的值复制到新分配的内存中,并且递归地拷贝所有子对象,才能让新对象拥有完全独立的内存空间。
内容的提问来源于stack exchange,提问作者user36278

