You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何自定义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]; // 子树指针直接复制,还是共享内存
    }
}
  1. 指针覆盖导致内存泄漏+浅拷贝
    你先用new string给fKey分配了新内存,但下一行fKey = aOtherNTree.fKey;直接把fKey指向了原对象的fKey地址——刚分配的内存没人管了(内存泄漏),而且新对象和原对象共享同一个fKey内存,这就是你看到地址没变的原因。

  2. 数组越界的未定义行为
    fNodes[N] = new NTree<string, N>;这里,fNodes是大小为N的数组,合法索引范围是0到N-1,访问fNodes[N]属于越界操作,会触发未定义行为(可能崩溃、数据乱掉)。

  3. 子树浅拷贝导致悬空指针
    循环里直接复制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;
}

额外需要修正的其他问题

  1. 普通构造函数的悬空指针问题
    你在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;
    }
    
  2. 析构函数的逻辑错误
    当前的析构函数没有正确释放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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:23:50