【问题标题】:How is vector implemented in C++C++中vector是如何实现的
【发布时间】:2011-03-05 02:54:19
【问题描述】:

我正在考虑如何从头开始实施std::vector

它如何调整矢量的大小?

realloc 似乎只适用于普通的旧结构,还是我错了?

【问题讨论】:

标签: c++ data-structures


【解决方案1】:
    ///Implement Vector class
    class MyVector {
        int *int_arr;
        int capacity;
        int current;
    public:
        MyVector() {
            int_arr = new int[1];
            capacity = 1;
            current = 0;
        }
        void Push(int nData);
        void PushData(int nData, int index);
        void PopData();
        int  GetData(int index);
        int  GetSize();
        void Print();
    };

    void MyVector::Push(int data)
    {
        if (current == capacity){
            int *temp = new int[2 * capacity];
            for (int i = 0; i < capacity; i++)
            {
                temp[i] = int_arr[i];
            }

            delete[] int_arr;
            capacity *= 2;

            int_arr = temp;
        }
        int_arr[current] = data;
        current++;
    }
    void MyVector::PushData(int data, int index)
    {
        if (index == capacity){
            Push(index);
        }
        else
            int_arr[index] = data;
    }
    void MyVector::PopData(){
        current--;
    }

    int MyVector::GetData(int index)
    {
        if (index < current){
            return int_arr[index];
        }
    }

    int MyVector::GetSize()
    {
        return current;
    }

    void MyVector::Print()
    {
        for (int i = 0; i < current; i++) {
            cout << int_arr[i] << " ";
        }
        cout << endl;
    }
    
    int main()
    {
        MyVector vect;
        vect.Push(10);
        vect.Push(20);
        vect.Push(30);
        vect.Push(40);

        vect.Print();

        std::cout << "\nTop item is "
            << vect.GetData(3) << std::endl;

        vect.PopData();
        vect.Print();

        cout << "\nTop item is "
            << vect.GetData(1) << endl;
        return 0;
    }

【讨论】:

    【解决方案2】:

    它是一个简单的模板类,它包装了一个原生数组。它使用malloc/realloc。相反,它使用传递的分配器(默认为std::allocator)。

    调整大小是通过分配一个新数组并从旧数组复制构造新数组中的每个元素来完成的(这样对非 POD 对象是安全的)。为了避免频繁分配,它们通常遵循非线性增长模式。

    更新:在 C++11 中,如果存储类型可能,元素将被移动而不是复制构造。

    除此之外,它还需要存储当前的“大小”和“容量”。大小是向量中实际有多少元素。容量是向量中可以的数量。

    因此,作为起点,向量需要看起来像这样:

    template <class T, class A = std::allocator<T> >
    class vector {
    public:
        // public member functions
    private:
        T*                    data_;
        typename A::size_type capacity_;
        typename A::size_type size_;
        A                     allocator_;
    };
    

    另一个常见的实现是存储指向数组不同部分的指针。这降低了end()(不再需要添加)的成本,但代价是稍微昂贵的size()调用(现在需要减法)。在这种情况下,它可能如下所示:

    template <class T, class A = std::allocator<T> >
    class vector {
    public:
        // public member functions
    private:
        T* data_;         // points to first element
        T* end_capacity_; // points to one past internal storage
        T* end_;          // points to one past last element
        A  allocator_;
    };
    

    我相信 gcc 的 libstdc++ 使用后一种方法,但两种方法同样有效且符合要求。

    注意:这忽略了一个常见的优化,其中空基类优化用于分配器。我认为这是实现细节的质量,而不是正确性的问题。

    【讨论】:

    • 那么这是否意味着在调整大小时分配的内存会暂时增加一倍?
    • 是的,在调整大小期间,有一段时间新内存已分配但旧内存尚未释放。
    • 是的,当使用具有千兆字节数据的向量并且内存用完时,这让我感到很痛苦..
    【解决方案3】:

    您可以通过调整数组大小来实现它们。 当数组变满时,创建一个两倍大小的数组并将所有内容复制到新数组中。不要忘记删除旧数组。

    至于从向量中删除元素,当你的数组变成四分之一时调整大小。这种策略可以防止在尝试以数组大小的一半重复插入和删除时出现任何性能故障。

    可以从数学上证明,对于 n 次插入,插入的摊销时间(平均时间)仍然是线性的,这与使用普通静态数组得到的渐近相同。

    【讨论】:

      【解决方案4】:

      【讨论】:

        【解决方案5】:

        来自Wikipedia,一个很好的答案。

        一个典型的向量实现在内部由一个指向 一个动态分配的数组,[2] 和可能持有的数据成员 向量的容量和大小。向量的大小是指 元素的实际数量,而容量是指大小 的内部数组。插入新元素时,如果新的大小 的向量变得大于其容量,重新分配 发生。[2][4]这通常会导致向量分配一个新的 存储区域,将先前保存的元素移动到新区域 存储空间,并释放旧区域。因为地址 元素在此过程中发生变化,任何引用或迭代器 向量中的元素变得无效。 [5]使用无效的 引用导致未定义的行为

        【讨论】:

          【解决方案6】:

          您需要定义“普通旧结构”的含义。

          realloc 本身只会创建一块未初始化的内存。它没有对象分配。对于 C 结构,这样就足够了,但对于 C++ 就不行了。

          这并不是说您不能使用 realloc。但是,如果您要使用它(请注意,在这种情况下您不会重新实现 std::vector!),您需要:

          1. 确保您在课堂上始终使用malloc/realloc/free
          2. 使用“placement new”初始化内存块中的对象。
          3. 在释放内存块之前显式调用析构函数来清理对象。

          这实际上与 vector 在我的实现 (GCC/glib) 中所做的非常接近,除了它使用 C++ 低级例程 ::operator new::operator delete 来执行原始内存管理而不是 malloc 和 free,重写realloc 例程使用这些原语,并将所有这些行为委托给一个分配器对象,该对象可以用自定义实现替换。

          由于矢量是一个模板,如果你想要一个参考,你实际上应该看看它的来源——如果你能克服下划线的优势,那么阅读它应该不会太难。如果您在使用 GCC 的 Unix 机器上,请尝试查找 /usr/include/c++/<i>version</i>/vector 或附近。

          【讨论】:

            【解决方案7】:

            调整向量的大小需要分配新的空间块,并将现有数据复制到新空间(因此,要求可以复制放入向量中的项目)。

            请注意,它确实使用new []——它使用传递的分配器,但这是分配原始内存所必需的,而不是像这样的对象数组new [] 确实如此。然后,您需要使用placement new 就地构造对象。 [编辑:好吧,从技术上讲,您可以使用new char[size],并将其用作原始内存,但我无法想象有人会编写这样的分配器。]

            当当前分配用完,需要分配新的内存块时,大小必须比旧大小增加一个常数因数,以满足摊销常数复杂度的要求push_back。尽管许多网站(和类似网站)称其为两倍大小,但 1.5 到 1.6 左右的因子通常效果更好。特别是,这通常会提高在未来分配中重新使用已释放块的机会。

            【讨论】:

            • 需要注意的是realloc可能有来自操作系统的高级支持,因为它不仅仅是一个普通的malloc/free(例如Windows上的HeapReAlloc),并且符合vector 的实现可以使用例如类型特征以检测类型是否为 POD,并在这种情况下使用 malloc/realloc/free 而不是 new/delete
            【解决方案8】:

            realloc 仅适用于堆内存。在 C++ 中,您通常希望使用免费商店。

            【讨论】:

            • “免费商店”?你这是什么意思?
            • FWIW,没有什么说你不能使用堆来创建免费存储。
            【解决方案9】:

            它分配一个新数组并复制所有内容。因此,如果您必须经常进行扩展,则效率非常低。如果必须使用 push_back(),请使用 reserve()。

            【讨论】:

            • 要挑剔,它会复制所有内容 =)
            • 现在,它移动了! :)
            猜你喜欢
            • 1970-01-01
            • 2014-08-17
            • 1970-01-01
            • 2017-09-15
            • 2021-06-08
            • 2014-02-13
            • 1970-01-01
            • 2020-11-26
            • 2011-08-07
            相关资源
            最近更新 更多