std::vector 如何破坏其对象?

Posted

技术标签:

【中文标题】std::vector 如何破坏其对象?【英文标题】:How does std::vector destruct its objects? 【发布时间】:2016-08-10 15:47:47 【问题描述】:

为了练习,我正在尝试实现自己的 std::vector。当前源码:http://pastebin.com/bE4kjzcb


这是我的课程大纲:

    Array() 使用 malloc() 分配一些内存 push_back(const T &t)添加一个元素,必要时调用realloc()~Array() 调用free() 释放内存。

这个模型的主要问题是,free() 回收了内存,但它不调用T 的析构函数(当T 是一个类而不是标准数据类型时)。

当向量内的元素是对象时,这可能会导致严重的资源泄漏。我对这个问题的解决方案是,在我free() 内存之前调用~T() 明确

我使用malloc() 的原因是,我正在尝试使用realloc()。如果我使用newdelete,内存使用会在重新分配时达到峰值。 (新缓冲区和旧缓冲区都存在的时刻。)

问: 这是一个糟糕的设计吗? std::vector 如何解决这个问题?我的向量类还有其他缺陷吗?

PS:现在不谈malloc()的多线程性能。

【问题讨论】:

如果你使用C++,不要使用mallocfree,使用newdelete 如何在push_back 中添加元素?如果您使用的是placement new,那么显式调用析构函数就可以了。 您的实施总体上并不太安全。您的 push_back 将原始未初始化内存重新解释为对象,然后分配给它。尝试将std::string 存储在您的Array 中,灾难肯定会随之而来。 "我使用 malloc() 的原因是,我正在尝试使用 realloc()。" 您不能将 realloc 用于存储类型不可轻易复制。 std::vector 使用作为模板参数的 allocator,默认为 std::allocator。从 C++17 及更高版本开始,它将通过std::allocator_traits 使用该分配器,调用std::allocator_traits:.construct 在指定存储中构造一个对象,并调用std::allocator_traits::destroy 来销毁一个对象。 【参考方案1】:

调用~T() 正是std::vector 处理问题的方式。

但是你确实有几个问题:

首先,push_back 需要使用placement new 将值复制构造到向量中。你不能只使用赋值。

其次,你不能调用realloc——如果对象有内部指针,它们最终会指向它们自身之外。您必须再次调用malloc,然后使用placement new 复制构造值,然后显式删除所有旧值,然后调用free 释放旧值。

(实际上,std::vector 本身并没有调用~T()。相反,它调用了负责...分配和释放内存的分配器。不过在内部,这就是通用的用途分配器会这样做。)

【讨论】:

并非如此。 vector 是一个分配器感知容器,所以没有主流实现会写 ~T() @uhohsomebodyneedsaupper 但是默认分配器会。 OK 我应该如何使用placement new? new (buffer[length]) T() ? 如果我使用placement new,是否还需要将分配的对象分配给buffer[length]?(由于object对象是在buffer上分配的) "如果我使用placement new,是否还需要将分配的对象分配给buffer[length]?"不,push_back 应该使用复制构造函数调用放置new。没有任务。【参考方案2】:

push_back(const T &t) 添加一个元素,必要时调用realloc()。

只要Ttrivially copiable 就可以,例如,尝试推回双链表并在重新分配后取一个并向后迭代 - 应用程序可能会崩溃。解决方案是重载函数两次,一次用于可复制的类型,另一次用于不可复制的对象。

与其他人相反,我很抱歉标准容器不使用realloc 来表示可轻松复制的对象。至少在 Windows 上,realloc 首先检查当前块是否可以容纳新大小,如果可以,它只会更新堆条目,从而导致巨大的性能提升(无复制)。

在我 free() 内存之前明确调用 ~T()。

是的,标准分配器就是这样做的。假设size 是对象计数,你迭代每一个并手动销毁它:

for (auto i=0U;i<size;i++)
   data[i].~T();

有趣的是,C++17 将添加 std::destruct 正是这样做的。

奖金: 在这里使用 new[]delete[] 将无济于事。通常,动态数组比实现容量所需的空间节省更多空间,多余的空间不会被活动对象填充,只有垃圾。

new[] 将用对象填充整个内存。容量不能以这种方式实现。每当有人推回新元素时,数组就会移动/复制整个对象。所以在 1000 个push_back 之后,将有 1000 个重新分配。我们想要O(log (n))的摊销时间。

即使是标准分配器也会调用new(size_t)malloc 而不是new[]

【讨论】:

【参考方案3】:

与其调用mallocfree,不如使用newdelete。调用delete 将确保调用实例dtor。 =)

【讨论】:

我想过,但我担心newdelete 是重新分配时的内存峰值。 (旧缓冲区和新缓冲区都必须存在片刻,以便我们可以复制数据。) 您将您的课程编写为模板。如果我给你的班级一个std::string 或基本上任何非 POD 类型,你的代码将使用malloc 失败。 C++ 不是 C。 @KelvinZhang 如果您想支持必须深度复制的类型,这就是您所需要的。 @KelvinZhang 所以你的问题实际上是关于vector如何实现重新分配,而不是malloc和free对吗? 您的印象似乎是 realloc 保留了原始分配的内存。事实并非如此。【参考方案4】:

如果默认构造函数是为刚刚分配的对象保留的并且如果 /move/copy 构造函数/赋值运算符和T 的析构函数传播刚刚分配的用户对象的信息。 std::vector 中的解决方案及其默认分配器仍然是一个更好的设计。

建筑

buffer = new T[capacity];

而不是

buffer = (T*)malloc(capacity * sizeof(T));

delete [] buffer;

而不是

free(buffer);

将自动调用每个对象的析构函数,如示例所示

class A 
public:
  ~A()  std::cout << "ok" << std::endl; 
;

int main() 
  A* a = new A[3];
  delete [] a;
  return 0;

此代码输出 3“ok”。那么 A 应该包含额外的字段和一个非默认构造函数来区分分配和用户构造。

【讨论】:

当您尝试在向量后面添加一个新值时,这将无法正常工作。您必须每次复制数组。 @MartinBonner 我认为长度可以演变直到容量,并且析构函数对默认构造函数创建的 T 对象(介于长度和容量之间的对象)不执行任何操作。对于重新分配,移动分配需要新的分配。是的 std::vector 完美地完成了这项工作。 但是关于容量的一点是,你没有在 size() 和 capacity() 之间构造元素——你只是分配了内存。 @MartinBonner 我同意你的评论。上面和 vector.tcc 中描述的解决方案更适合仅分配 size() 和 capacity() 之间的元素。【参考方案5】:

这里有一个例子,它或多或少是如何工作的 std::vector:

#ifndef __STDVECTOR__
#define __STDVECTOR__

#include <iostream>

using namespace std;

template <typename T>
    class StdVector
        private:
            T *buffer;
            unsigned int capacity;
        public:
            //Constructor.
            StdVector()
                capacity=0;
                buffer=new T[capacity];
            
            //Copy constructor.
            StdVector(const StdVector &asv)
                int i;
                
                capacity=asv.getCapacity();
                buffer=new T[asv.getCapacity()];
                for (i=0; i<capacity; i++)
                    buffer[i]=asv[i];
                
            
            //Destructor.
            ~StdVector()
                delete []buffer;
            
            void push_back(T obj)
                StdVector oldSV(*this);
                int i;
                
                capacity++;
                delete []buffer;
                buffer=new T[capacity];
                for (i=0; i<oldSV.getCapacity(); i++)
                    buffer[i]=oldSV[i];
                
                buffer[i]=obj;
            ;
            T getBuffer() const
                if (capacity==0)
                    throw exception();
                
                return *buffer;
            ;
            T &operator[](int index) const
                if (index>=capacity)
                    //Out of range.
                    throw exception();
                
                else
                    return buffer[index];
                
            
            StdVector &operator=(const StdVector &obj)
                capacity=obj.getCapacity();
                delete []buffer;
                buffer=new T[capacity];
                buffer=obj.getBuffer();
                return *this;
            
            unsigned int getCapacity() const
                return capacity;
            ;
    ;
 
#endif

int main()
    try
        StdVector<int> test;
        StdVector<string> test2;
        unsigned int i;
        
        test.push_back(5);
        test.push_back(4);
        test.push_back(3);
        test.push_back(2);
        test.push_back(1);
        test.push_back(0);
        test.push_back(-1);
        test.push_back(-2);
        test.push_back(-3);
        test.push_back(-4);
        test.push_back(-5);
        for (i=0; i<test.getCapacity(); i++)
            cout << test[i] << endl;
        
        test2.push_back("Hello");
        test2.push_back(" ");
        test2.push_back("World");
        test2.push_back(".");
        cout << "---------------" << endl;
        for (i=0; i<test2.getCapacity(); i++)
            cout << test2[i];
        
        cout << endl;
    
    catch(...)
        cout << "Exception." << endl;
    
    return 0;

它打印:

5

4

3

2

1

0

-1

-2

-3

-4

-5

---------------

你好世界。

也许我有一些错误。如果你知道,请告诉我。

【讨论】:

以上是关于std::vector 如何破坏其对象?的主要内容,如果未能解决你的问题,请参考以下文章

std::vector pop_back() 实现

如何桥接 JavaScript(参差不齐)数组和 std::vector<std::vector<T>> 对象?

如何避免 std::vector<> 初始化其所有元素?

如何确定`range :: view`对象和`std :: vector`之间的等价?

如何在 c++11 中使用 std::vector 使其更快?

如何将 xarray 转换为 std::vector?