std::vector 插入是如何实现的? C++

Posted

技术标签:

【中文标题】std::vector 插入是如何实现的? C++【英文标题】:How is std::vector insert implemented? C++ 【发布时间】:2014-08-17 00:01:38 【问题描述】:

最近在重读ISO C++标准,发现很有意思的注释:

请注意,对于 std::vectorstd::vector<T> 类型 T 的唯一约束是 T 类型必须具有复制构造函数。实际上,如果插入时vector的内存已满,则分配size = 2 * oldSize的新内存(这取决于实现),然后将其中的旧元素复制并插入该元素。

但是等等??

要分配类型的新内存,我们需要这样的东西,ptr = new T[2*size];

    这是怎么做到的,因为T类型可能没有默认构造函数? 然后Assignment,分配完内存后我们必须将旧值赋给新内存,对吧? 考虑到这两件事,std::vector 如何使用“ONLY COPY CONSTRUCTOR”来做到这一点?使用了哪些实现和语言习语?

【问题讨论】:

数组-new没有完成。正如您刚刚发现的那样,Array-new 完全是该语言的错误功能并且完全没用。相反,内存分配和对象构造是完全独立完成的。 如果没有提供明确的默认编译器,编译器会生成一个。 @littleadv 如果该类具有任何类型的用户定义构造函数,则没有编译器生成的默认构造函数 @KerrekSB 为什么你会说某些东西完全没用,只是因为它不适合这种情况? Array-new 适合分配数组。您可以争辩说显式手动分配是不好的(在这种情况下,您反对newdeletenew[]delete[] 以及可能的原始指针),但这与争论只有数组-@987654336 不同@ 不好。 @immibis:动态数组在概念上被破坏了。你不能在不知道它的大小的情况下使用动态数组,所以你必须单独携带大小信息,这违反了封装。雪上加霜的是,编译器无论如何都需要复制大小信息才能调用析构函数。简短的回答是,只需使用std::vector 【参考方案1】:

通过调用 allocator 函数 allocate() 来获取原始内存,然后调用分配器 construct( iterator, val ) 使用 placement new 复制构造一个元素,即类似于以下内容:

/* approach similar to std::uninitialized fill taken */
template<typename T, typename A >
vector<T,A>::vector( size_type n, const T& val, const A& a) : alloc( a)  // copy the allocator

    /* keep track of which elements have been constructed
     * and destroy those and only those in case of exception */
    v = alloc.allocate( n); // get memory for elements
    iterator p;             // declared before try so it is still valid in catch block

    try 
        iterator end = v + n;
        for( p = v; p != end; ++p)
            alloc.construct( p, val); /* construct elements (placement new):
                                      e g. void construct( pointer p, const T& val) 
                                       ::new((void *)p) T( val);  */
        last = space = p;
     catch( ...) 
        for( iterator q = v; q != p; ++q)
            alloc.destroy( q);       /* destroy constructed elements */
        alloc.deallocate( v, n);     /* free memory */
        throw;                       /* re-throw to signal constructor that failed */
    

在 C++ 中,分配器用于将必须分配内存的算法和容器的实现者与物理内存的细节隔离开来。

也可以采取直接使用uninitialized_fill的方式:

 std::uninitialized_fill( v, v + n, val); /* copy elements with (placement new):
                                             e g. void construct( pointer p,
                                                                  const T& val) 
                                              ::new((void *)p) T( val);  */

这在 Bjarne Stroustrup 的“C++...3rd edition”中有更详细的描述。 Here是基于此编写的示例。

【讨论】:

这很挑剔,但allocate 的返回类型为std::allocator_traits&lt;A&gt;::pointer,而construct 需要C *(对于任意对象类型C)。前者不必是后一种形式(例如,它可以是用户定义的类型)。 +1 用于显示完整且可读的代码,包括注释中可能的 alloc.construct() 内联。请注意,OP 询问了向量调整大小时的情况,它还需要销毁旧数组中的元素。但是一旦理解了上面的代码,这样做是微不足道的。 也可能值得提供一个示例alloc.destroy(即q-&gt;~T())。此外,iterators 不一定是指针。除此之外,很好的答案,+1。 @Mankarse 但碰巧 std::vector::iterator 是一个指针 @ratchetfreak:std::vector::iterator 不一定是指针。在许多实现中它可能是,是的,但标准不保证这一点。【参考方案2】:

作为一般规则,标准容器分开分配 从初始化开始(你写的任何容器也应该如此)。 标准容器使用一种非常复杂的机制来 允许自定义分配和初始化,但是 在默认情况下,归结为使用 operator new/operator delete函数分配内存, 放置 new 来初始化它,并显式调用 析构函数来销毁对象。换句话说,而不是 顺序:

p = new T[n];
//  ...
delete [] p;

它使用:

p = operator new( n * sizeof( T ) );
for ( int i = 0; i != n; ++ i ) 
    new ( p + i ) T( otherValue );


//  ...
for ( int i = 0; i != n; ++ i ) 
    p->~T();

operator delete( p );

(这是一个激进的简化,以显示基本概念。 在实践中,由于例外原因,它会更复杂 例如安全性。)

【讨论】:

【参考方案3】:

想想 emplace_back() :很可能 vector 分配了一块新的未初始化内存,然后运行 ​​Placement new 以就地复制构造对象。

【讨论】:

以上是关于std::vector 插入是如何实现的? C++的主要内容,如果未能解决你的问题,请参考以下文章

将 OpenCv Mat 插入 C++ std::vector

C++ STL应用与实现2: 如何使用std::vector

C++ STL应用与实现2: 如何使用std::vector

C++ STL应用与实现2: 如何使用std::vector

具有来自多个类的值的 C++ std::vector

如何使我的 std::vector 实现更快? [复制]