当使用模板类制作自定义向量时,我应该如何处理析构函数?

How should I deal with the destructor, when making a custom vector with a template class?

提问人:Hashnut 提问时间:6/10/2022 最后编辑:Peter MortensenHashnut 更新时间:6/10/2022 访问量:89

问:

我尝试使用模板类制作自定义 Vector 类。

我希望我可以把我的放到一个变量中。至少这是我所希望的......但它在析构函数代码处不断崩溃。Vector<int>Vector<Vector<int>>

这是我的代码。

#include <iostream>
#include <string>

template <typename T>
class Vector {

    T* data;
    int capacity;
    int length;

public:

    typedef T value_type;

    Vector() {}

    Vector(int n) : data(new T[n]), capacity(n), length(0) {}

    void push_back(T input) { 
        data[length++] = input;
    }

    T operator[](int i) { return data[i]; }

    virtual ~Vector() { if (data) delete[] data; }
};

int main() {
    Vector<Vector<int>> v(3);
    Vector<int> vv(4);
    v.push_back(vv);
}

所以我想,也许我应该使用复制构造函数,因为似乎问题是之前被删除了。好吧,如果我只是注释掉析构函数代码,它会起作用,但这对我来说似乎不对......vvv

所以我做了一个自定义的复制构造函数,如下所示:

Vector(const T& other) { 

}

但它给了我一个错误,说“模棱两可的超载”......回头看,这当然是错误的,因为 of 与 of 不同......TdataTother

如何使我的自定义类正常工作?(即我想push_back按照我的预期工作......Vector

C++ 向量 容器析 构函数 法则

评论

1赞 UnholySheep 6/10/2022
const T& other -> const Vector<T>& other?
2赞 fabian 6/10/2022
除非初始化 ,否则此成员变量的值可以是任意的。除非此值恰好为 null,否则调用它会导致未定义的行为,在这种情况下会导致程序崩溃。(默认构造函数不会将其初始化为 null。顺便说一句:可以用 null 作为操作数来调用;在这种情况下,它什么都不做。检查析构函数中是否为 null 是不必要的。datadelete[]delete[]data
0赞 fabian 6/10/2022
注意:如果你想使 工作,你应该从运算符那里引用: 此外,我建议对索引使用无符号整数类型。通常用于此目的,这也是 / 的首选类型。v[0].push_back(1);Vector<Vector<int>>[]T& operator[](int i) { ... }size_tcapacitylength
0赞 Quimby 6/10/2022
正确实现向量并非易事,因为它必须涉及放置。new
0赞 Peter 6/10/2022
类构造函数显式执行动态内存分配。您需要确保所有构造函数(包括复制/移动构造函数)在需要时分配内存,并且赋值(复制/移动)运算符在需要时正确重新分配内存,否则复制 u 的实例将导致析构函数释放一些内存两次 - 这会导致未定义的行为。为这些函数隐式生成的默认值不会这样做。有关详细信息,请查看“三法则”或(C++11 及更高版本)“五法则”。Vector

答:

5赞 Botje 6/10/2022 #1

默认构造函数使对象完全未初始化。

考虑一下当您声明

Vector<int> foo;

foo本质上得到一个随机的内存地址,如 、 和 。如果你释放它,这将产生烟花。datalengthcapacity

也许您通过始终使用预定义大小创建矢量来回避这个问题。幸运的是,试图创建/摧毁一个会暴露这一点,因为你的容器内部仍然包含这些滴答作响的定时炸弹。Vector<Vector<int>>Vector<int>[]

6赞 einpoklum 6/10/2022 #2

一般问题

在类设计中,特别是当涉及内存/资源分配时,通常需要遵循“五法则”(在 C++ 11 之前曾经是“三法则”):

如果实现以下任何一项:

  • 析构函数
  • 复制/移动赋值运算符
  • 复制/移动构造函数

那么你可能需要实现所有这些。

原因是它们中的每一个都可能需要一些资源管理逻辑,而不是语言给出的默认值。

对于您的班级,这五种方法的签名将是:

方法 签名
Copy 构造函数 Vector(const Vector&)
Move 构造函数 Vector(Vector&&)
复制赋值运算符 Vector& operator=(const Vector&)
移动赋值运算符 Vector& operator=(Vector&&)
破坏者 ~Vector()virtual ~Vector()

您的特定班级

在您的具体案例中,存在几个具体问题:

  • 如@UnholySheep所示,您错误地声明了复制构造函数。
  • 您实现了一个默认构造函数;但是 - 它不分配任何东西,也不初始化任何东西!指针包含任意垃圾,当您尝试释放它时,可能会发生不好的事情。data
  • 您正在执行相当多的值复制,对于外部向量来说,这些值将是值 - 这可能会变得昂贵。TVector<int>
  • 即使您修复了上述问题,您仍然应该实现“五法则”中缺少的方法。