【问题标题】: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 及更高版本中,三规则(通常)变为五规则。

标签: c++ arrays pointers


【解决方案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) 它允许以后添加的东西更简单。
猜你喜欢
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 2015-11-30
  • 1970-01-01
  • 2014-03-15
  • 2014-07-23
  • 2017-06-11
  • 1970-01-01
相关资源
最近更新 更多