我刚刚开始使用我的数据结构,我正在实现某种排列列表(~=std::vector)。我想知道pop_back()是如何实现的,因此它具有恒定的复杂性。它必须将大小缩小1,并删除最后一个元素。现在我在某个地方看到,它基本上将大小缩小了1,并通过一些迭代器/指针破坏了最后一个元素,但是为什么我们不能以同样的方式实现pop_front(),只将指针重定向到第一个元素到下一个元素呢?
template <typename T>
ArrayList<T>& ArrayList<T>::pop_back()
{
--size_;
auto p = new T[size_];
std::copy(elements_,elements_+size_,p);
delete [] elements_; //elements_ being my pointer
elements_ = p;
return *this;
}
发布于 2020-03-24 15:03:54
pop_back()通常不是这样实现的。虽然这是一个实现细节,但通常您的list/vector/dynamic数组会跟踪两个大小:一个是列表的实际大小,另一个是容量。容量是基础分配内存的大小。现在,pop_back()只需减少1的大小,并在最后一个元素上运行析构函数(不要混淆销毁,即调用~T()
方法和delete
操作符)。它不会转移任何东西。容量保持不变。整个操作不取决于列表的大小(与您的实现不同)。
请注意,您不能以一种简单的方式对pop_front()进行同样的操作。您必须同时跟踪列表的起始和结束以及底层内存(取决于方法:要么存储大小和容量,要么在运行时计算它们)。这就需要更多的内存和更多的cpu操作。而且这种结构也变得很奇怪。您知道容量,知道大小,但实际上不知道在调整大小之前可以执行多少push_back() (您只知道这个数字是以“容量减去大小”为界的,但它可以更小)。除非您将此信息添加到您的结构中,否则将再次占用内存或cpu。
附带注意:如果要采用原始析构函数的方式,那么根本不要使用delete[]
操作符。delete操作基本上是“调用析构函数+释放内存”。因此,如果手动销毁,那么对delete[]
的额外调用将导致未定义的行为。除非您实际分配char
内存(不管T
如何)并使用安置新 (这还需要一些手动大小和偏移量计算)。这是实现这种向量的好方法,尽管需要额外的注意(手动内存跟踪是b*h)。
发布于 2020-03-24 15:11:20
在实现O(1)复杂性的pop_back()
时遇到困难的原因是,通过使用new[]
和delete[]
,您将包含的对象的生存期与对象的存储绑定在一起。我的意思是,当使用new T[n];
创建原始动态分配数组时,会发生两件事: 1)分配存储,2)在该存储中构造对象。相反,delete[]
将首先销毁所有对象(调用其析构函数),然后将底层内存释放回系统。
C++确实提供了单独处理存储和生命周期的方法。这里涉及的主要内容是原始存储、放置new
、指针转换和头痛。
在您的pop_back()
函数中,您似乎希望只销毁数组中的一个对象,而不破坏所有其他对象或释放它们的存储空间。不幸的是,使用new[]
和delete[]
是不可能的。std::vector
和其他一些容器的工作方式是使用较低级别的语言特性。通常,这是通过分配一个连续的原始内存区域(输入为unsigned char
或std::byte
,或者使用像std::aligned_storage
这样的助手)、进行大量的簿记、安全检查和额外工作,然后使用new
在原始内存中构造一个对象来完成的。要访问对象,您需要将偏移量计算到原始内存的(数组)中,并使用reinterpret_cast
来生成指向放置在其中的对象的指针。这还需要显式调用对象的析构函数。在这个较低的层次上工作,对象生命周期中的每一个细节都掌握在您的手中,而且非常繁琐和容易出错。我不建议这么做。但这是可能的,它允许std::vector::pop_back()
在O(1)复杂性中实现(并且不使迭代器对以前的元素无效)。
发布于 2020-03-24 15:08:33
当您在标准向量上pop_back
时,实际上并不释放相关的内存。只有最后一个元素被销毁,size
被减少,但capacity
没有被销毁。没有其他元素被复制或移动。这很难用自定义容器进行复制,因为您不能delete
或销毁数组的单个元素。
因此,std::vector<T>
实际上并不使用T
数组。它分配未初始化的原始内存(类似于std::aligned_storage
),并执行new
以根据需要在分配中创建元素。pointer new
是new
的一个版本,它不分配,而是提供了一个指针,它应该在其中构造对象。这意味着对象的生存期与它的分配没有直接关联,并且唯一地允许您单独销毁元素,而不通过调用析构函数来释放它的底层内存。在内部,它看起来类似于pop_back() { back().~T(); --size; }
。
https://stackoverflow.com/questions/60833595
复制相似问题