如何正确分配动态一维数组作为类的成员?

Posted

技术标签:

【中文标题】如何正确分配动态一维数组作为类的成员?【英文标题】:How to properly allocate a dynamic one-dimensional array as a member of a class? 【发布时间】:2021-08-04 00:59:03 【问题描述】:

到目前为止,这是我的课程:

template <typename T>
class myClass

private:
    size_t capacity_;
    size_t size_;
    T* the_array;
public:
    myClass(size_t cap) 
        capacity_ = cap;
        size_ = 0;
        the_array = new T[capacity_];
    
    ~myClass()  clear(); 

    virtual size_t capacity() const 
        return capacity_;
    

    virtual size_t size() const 
        return size_;
    

    virtual bool clear() 
        delete[] the_array;
        return true;
       
;

我试图在构造函数中分配数组,但是当我创建一个 myClass 对象时,它的 the_array 不是具有我请求的容量的数组。调试时,the_array 只是一个指向 T 的指针。我不确定问题出在构造函数中还是在我为指向 the_array 的指针的私有变量的声明中。

【问题讨论】:

如果你想避免头痛,可以使用std::vector 顺便说一句,你有virtual 成员函数但没有virtual 析构函数,这通常是一个不好的方法。我建议在这里删除virtual,或者如果您确实需要动态调度,请将析构函数标记为virtual Handy extra reading。 TL;DR 版本,如果你有析构函数,你可能需要一个复制构造函数和赋值运算符来配合它。 “the_array 只是一个指向 T 的指针”——我要告诉你一个大秘密:在 C++ 中没有数组这样的东西。这是一个海市蜃楼,一个幻觉,一个花招。数组实际上只是指向数组第一个值的指针。您的调试器只是向您展示了真实的、红色的状态。显示代码的唯一问题是clear() 应该清除已删除数组的指向nullptr 的指针,并且该类需要一个复制构造函数和一个赋值运算符。 查找“C++ 三规则”。要点是,如果您的类直接管理需要非平凡破坏(即释放资源)的资源(在您的情况下为内存),那么您的类还需要一个复制构造函数和一个赋值运算符。在 C++11 及更高版本中,三规则(通常)变为五规则。 【参考方案1】:

当我创建一个 myClass 对象时,它的 the_array 不是具有我要求的容量的数组。

是的,是的。好吧,无论如何,指向这样一个数组的指针。

调试时,the_array 只是一个指向 T 的指针。

它是指向动态分配数组的指针(第一个元素),该数组包含T 类型的元素。

我不确定问题出在构造函数中还是在我为指向 the_array 的指针的私有变量的声明中。

两者都没有。问题在于你对现实的理解。

new[] 分配一个数组来保存指定类型的指定数量的元素,然后返回一个指向该内存的指针。另一方面,调试器无法知道指针指向的内存类型。它所知道的只是指针指向取消引用类型的实例,因此默认情况下它只能显示该单个实例的数据。但是你知道指针实际上指向的是一个数组,所以你需要明确地告诉调试器这个事实,以便它可以显示数组中的其他元素。大多数调试器都提供一个命令,让您指定在所指向的内存地址中存在多少数组元素。

您显示的代码正确地创建了数组。该代码只是在之后管理数组的方式不完整,因为它不遵循Rule of 3/5/0。它需要实现(或禁用)复制构造函数和复制赋值运算符。对于 C++11 及更高版本,它还应实现移动构造函数和移动赋值运算符。例如:

#include <utility>
#include <algorithm>

template <typename T>
class myClass

private:
    size_t capacity_;
    size_t size_;
    T* array_;

public:
    myClass(size_t cap = 0) 
        capacity_ = cap;
        size_ = 0;
        array_ = new T[capacity_];
    

    myClass(const myClass& src) 
        capacity_ = src.capacity_;
        size_ = src.size_;
        array_ = new T[capacity_];
        std::copy_n(src.array_, size_, array_);
    

    myClass(myClass&& src) 
        capacity_ = std::exchange(src.capacity_, 0);
        size_ = std::exchange(src.size_, 0);
        array_ = std::exchange(src.array_, nullptr);
    

    ~myClass()  clear(); 

    myClass& operator=(myClass rhs) 
        capacity_ = std::exchange(rhs.capacity_, 0);
        size_ = std::exchange(rhs.size_, 0);
        array_ = std::exchange(rhs.array_, nullptr);
        return *this;
    

    size_t capacity() const 
        return capacity_;
    

    size_t size() const 
        return size_;
    

    void clear() 
        delete[] array_;
        array_ = nullptr;
        capacity_ = size_ = 0;
       
;

话虽如此,您应该使用std::vector 而不是new[],然后所有这些细节都会自动为您处理,因为std::vector 遵循3/5/0 规则,并且编译器-在您的类中生成的构造函数和赋值运算符足以正确管理向量,例如:

#include <vector>

template <typename T>
class myClass

private:
    std::vector<T> vec_;

public:
    myClass(size_t cap = 0) 
        vec_.reserve(cap);
    

    size_t capacity() const 
        return vec_.capacity();
    

    size_t size() const 
        return vec_.size();
    

    void clear() 
        vec_.clear();
       
;

【讨论】:

顺便说一句:vector-using 版本不需要额外的复制、移动和销毁逻辑,因为编写 vector 的人已经为您完成了所有工作,您的班级将完成默认使用它。从中得出的结论是,您应该在尽可能靠近需要它的资源的地方实现复制移动销毁逻辑,通常添加一个除了复制移动销毁之外什么都不做的额外对象,因为 A) 没有它会更容易以后添加的东西和 B) 它允许以后添加的东西更简单。

以上是关于如何正确分配动态一维数组作为类的成员?的主要内容,如果未能解决你的问题,请参考以下文章

分配一维动态数组or 二维动态数组的方法以及学习 new 方法or vector

js如何定义二维数组,一维的key为动态值。就是要处理二级菜单显示。

如何将二维数组的一维分配给VBA中的新一维数组

java——数组

C语言中怎样定义动态一维数组

如何在C++中创建一维动态数组