简体   繁体   English

std :: vector如何破坏它的对象?

[英]How does std::vector destruct its objects?

I'm trying to implement my own std::vector for sake of practice. 为了练习,我正在尝试实现自己的std :: vector。 Current source code: http://pastebin.com/bE4kjzcb 当前源代码: http//pastebin.com/bE4kjzcb


Here is an outline of my class: 这是我班级的概要:

  1. Array() allocate some memory using malloc() Array()使用malloc()分配一些内存
  2. push_back(const T &t) add one element, call realloc() if necessary. push_back(const T &t)添加一个元素,必要时调用realloc()
  3. ~Array() call free() to release memory. ~Array()调用free()来释放内存。

The major issue with this model is, free() recycles the memory, but it doesn't call T 's destructor(when T is a class rather than standard data type). 这个模型的主要问题是free()循环内存,但它不调用T的析构函数(当T是类而不是标准数据类型时)。

This could cause severe resource leak when elements inside vectors are objects. 当向量内的元素是对象时,这可能导致严重的资源泄漏。 My solution to this problem is, call ~T() EXPLICITLY before I free() the memory. 我解决这个问题的方法是,在free()内存之前调用~T() EXPLICITLY

The reason I'm using malloc() is, I'm trying to utilize realloc() . 我使用malloc()的原因是,我正在尝试使用realloc() If I use new and delete , memory usage will peak when reallocing. 如果我使用newdelete ,则重新分配时内存使用率将达到峰值。 (The moment when both the new buffer and old buffer are present.) (新缓冲区和旧缓冲区都存在的时刻。)

Q: Is that a bad design? 问:这是一个糟糕的设计吗? how does std::vector resolve this problem? std::vector如何解决这个问题? Is there other flaws in my vector class? 我的矢量类中还有其他缺陷吗?

PS:Let's not talk about multithread performances of malloc() now. PS:现在不要谈论malloc()多线程性能。

Calling ~T() is exactly how std::vector handles the problem. 调用~T() 正是 std::vector处理问题的方式。

You do however have a couple of problems: 但是你有几个问题:

Firstly, push_back needs to use placement new to copy-construct the value into the vector. 首先, push_back需要使用placement new来将值复制 - 构造到向量中。 You can't just use assignment. 你不能只使用作业。

Secondly, you can't call realloc - if the object have internal pointers, they are going to end up pointing outside them selves. 其次,你不能调用realloc - 如果对象有内部指针,它们最终会指向自己的外部。 You must call malloc again, then use placement new to copy-construct the values, then explictly delete all the old values, then call free to release the old value. 您必须再次调用malloc ,然后使用placement new复制构造值,然后显式删除所有旧值,然后调用free释放旧值。

(Actually, std::vector doesn't call ~T() itself. Instead it calls the allocator which is responsible for ... allocating and deallocating memory. Internally though, that is how general purpose allocators do it.) (实际上, std::vector不会调用~T()本身。而是调用分配器来负责... 分配和释放内存。但在内部,这就是通用分配器的用法。)

push_back(const T &t) add one element, call realloc() if necessary. push_back(const T&t)添加一个元素,必要时调用realloc()。

It is ok as long as T is trivially copiable , for example, try to push back double linked lists and after reallcoation take one and iterate backwards - the app will probably crash. 只要T可以trivially copiable ,就可以了,例如,尝试推回双链表,并且在重新完成后需要一个并向后迭代 - 应用程序可能会崩溃。 the solution is to overload the function twice, one for types which are trivialy copiable, and one for objects that are not. 解决方案是将函数重载两次,一个用于可琐事可复制的类型,另一个用于非琐事的对象。

As opposed to others, I'm quite sorry that the standard containers do not use realloc for objects which are trivially copiable. 与其他人相反,我很遗憾标准容器不会将realloc用于可以轻易处理的对象。 at least on Windows, realloc first checks if the current block can hold the new size and if so - it just updates the heap entry, causing huge performance boost (no copying). 至少在Windows上, realloc首先检查当前块是否可以保持新的大小,如果是 - 它只是更新堆条目,从而导致巨大的性能提升(无需复制)。

call ~T() EXPLICITLY before I free() the memory. 在我释放()内存之前调用~T()显式。

Yes, this is how the standard allocator does it. 是的,这就是标准分配器的工作方式。 assume size is the object count, you iterate each one and destruct it manually: 假设size是对象计数,您迭代每个并手动销毁它:

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

Interestingly, C++17 will add std::destruct which does exactly that. 有趣的是,C ++ 17将添加std::destruct ,它就是这样做的。

Bonus: Using new[] and delete[] will not help here. 奖励:使用new[]delete[]在这里没有帮助。 usually, dynamic arrays saves more space than what they need to achieve capacity ,the extra space is not filled with live objects, only junk. 通常,动态数组比实现容量所需的空间节省更多空间 ,额外的空间不是用活动对象填充,只是垃圾。

new[] will fill the entire memory with objects. new[]将用对象填充整个内存。 capacity cannot be implemented this way. 容量无法以这种方式实现。 the array will move/copy the entire objects whenever someone pushes back new elements. 每当有人推回新元素时,数组将移动/复制整个对象。 so after 1000 push_back 's, there will be 1000 reallocations. 所以在1000次push_back之后,将有1000次重新分配。 we want amortized time of O(log (n)) . 我们希望摊销时间为O(log (n))

even the standard allocator will call new(size_t) or malloc and not new[] 甚至标准的分配器也会调用new(size_t)malloc而不是new[]

Rather than calling to malloc and free , prefer to use new and delete . 而不是调用mallocfree ,更喜欢使用newdelete Calling to delete will ensure that the instance dtor is called. 调用delete将确保调用实例dtor。 =) =)

At the limit it is possible to use new [] if the default constructor is reserved for just allocated objects and if the /move/copy constructors/assignment operators and destructor of T propagate the information just allocated or user object . 如果默认构造函数仅为已分配的对象保留,并且T的/ move / copy构造函数/赋值运算符和析构函数传播刚分配的信息或用户对象 ,则可以使用new [] The solution in std::vector and its default allocator is nevertheless a far better design. std :: vector中的解决方案及其默认分配器是一个更好的设计。

The construction 那个工程

buffer = new T[capacity];

instead of 代替

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

and

delete [] buffer;

instead of 代替

free(buffer);

will automatically call the destructor of each object as on the example 将自动调用每个对象的析构函数,如示例所示

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

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

this code outputs 3 "ok". 此代码输出3“ok”。 Then A should contain additional fields and a non default constructor to differentiate allocation from user construction. 然后,A应包含其他字段和非默认构造函数,以区分分配和用户构造。

Here you have an example how it works more or less std::vector: 这里有一个例子,它是如何工作的或多或少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;
}

It prints: 它打印:

5

4 4

3 3

2 2

1 1

0 0

-1 -1

-2 -2

-3 -3

-4 -4

-5 -5

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

Hello World. 你好,世界。

Maybe i have some error. 也许我有一些错误。 If you know, say me pls. 如果你知道的话,请跟我说。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM