首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >向量pop_back()实现

向量pop_back()实现
EN

Stack Overflow用户
提问于 2020-03-24 14:58:47
回答 4查看 2.5K关注 0票数 3

我刚刚开始使用我的数据结构,我正在实现某种排列列表(~=std::vector)。我想知道pop_back()是如何实现的,因此它具有恒定的复杂性。它必须将大小缩小1,并删除最后一个元素。现在我在某个地方看到,它基本上将大小缩小了1,并通过一些迭代器/指针破坏了最后一个元素,但是为什么我们不能以同样的方式实现pop_front(),只将指针重定向到第一个元素到下一个元素呢?

代码语言:javascript
运行
复制
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;
}
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 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)。

票数 5
EN

Stack Overflow用户

发布于 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 charstd::byte,或者使用像std::aligned_storage这样的助手)、进行大量的簿记、安全检查和额外工作,然后使用new在原始内存中构造一个对象来完成的。要访问对象,您需要将偏移量计算到原始内存的(数组)中,并使用reinterpret_cast来生成指向放置在其中的对象的指针。这还需要显式调用对象的析构函数。在这个较低的层次上工作,对象生命周期中的每一个细节都掌握在您的手中,而且非常繁琐和容易出错。我不建议这么做。但这是可能的,它允许std::vector::pop_back()在O(1)复杂性中实现(并且不使迭代器对以前的元素无效)。

票数 3
EN

Stack Overflow用户

发布于 2020-03-24 15:08:33

当您在标准向量上pop_back时,实际上并不释放相关的内存。只有最后一个元素被销毁,size被减少,但capacity没有被销毁。没有其他元素被复制或移动。这很难用自定义容器进行复制,因为您不能delete或销毁数组的单个元素。

因此,std::vector<T>实际上并不使用T数组。它分配未初始化的原始内存(类似于std::aligned_storage),并执行new以根据需要在分配中创建元素。pointer newnew的一个版本,它不分配,而是提供了一个指针,它应该在其中构造对象。这意味着对象的生存期与它的分配没有直接关联,并且唯一地允许您单独销毁元素,而不通过调用析构函数来释放它的底层内存。在内部,它看起来类似于pop_back() { back().~T(); --size; }

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60833595

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档