STL中std::vector的reserve()实现

3
考虑以下来自《C++程序设计语言》(第四版)Bjarne Stroustrup的std::vector::reserve()实现:
template<class T, class A>
void vector<T,A>::reserve(size_type newalloc)
{
    if (newalloc<=capacity()) return;
    vector_base<T,A> b {vb.alloc,newalloc};          // get new storage
                                                     // (see PS of question for details on vb data member)

    T* src  = elem;                                  // ptr to the start of old storage
    T* dest = b.elem;                                // ptr to the start of new storage       
    T* end  = elem+size();                           // past-the-end ptr to old storage
    for (; src!=end; ++src, ++dest) {
        new(static_cast<void*>(dest)) T{move(*src)}; // move construct
        src–>~T();                                   // destroy
    }

    swap(vb,b);                                      // install new base (see PS if needed)
} // implicitly release old space(when b goes out of scope)

注意,在循环中,对于向量中的每个元素,至少会调用一次构造函数和析构函数(如果元素的类具有基类或如果该类或其基类具有具有构造函数的数据成员,则可能触发更多此类调用)。 (在书中,“for-loop”实际上是一个单独的函数,但我在这里为了简单起见将其注入到reserve()中。)

现在考虑我的替代建议:

template<class T, class A>
void vector<T,A>::reserve(size_type newalloc)
{
    if (newalloc<=capacity()) return;
    vector_base<T,A> b {vb.alloc,newalloc};    // get new space

    memcpy(b.elem, elem, sz);                  // copy raw memory
                                               // (no calls to ctors or dtors)

    swap(vb,b);                                // install new base
} // implicitly release old space(when b goes out of scope)

在我看来,最终结果似乎是相同的,减少了对构造函数/析构函数的调用。

是否存在这种情况,这种替代方案会失败?如果有,请问哪里出现了缺陷?


P.S. 我认为这并不太相关,但以下是 vectorvector_base 类的数据成员:

// used as a data member in std::vector
template<class T, class A = allocator<T> >
struct vector_base {                    // memory structure for vector
     A alloc;       // allocator
     T* elem;       // start of allocation
     T* space;      // end of element sequence, start of space allocated for possible expansion
     T* last;       // end of allocated space

     vector_base(const A& a, typename A::size_type n)
         : alloc{a}, elem{alloc.allocate(n)}, space{elem+n}, last{elem+n} { }
     ~vector_base() { alloc.deallocate(elem,last–elem); } // releases storage only, no calls 
                                                          // to dtors: vector's responsibility        
     //...
};

// std::vector
template<class T, class A = allocator<T> >
class vector {
     vector_base<T,A> vb;            // the data is here
     void destroy_elements();
public:
     //...
};

如果元素保留对自身的指针或依赖于自己的地址,则此方法将失败。如果它们例如复制了此指针,或将此指针复制到lambda表达式中等,那么这就足够了。 - AaronI
@AaronI 这个提议是UB,简单明了,这就是为什么更懂的Bjarne和他的团队没有使用它。话虽如此...任何可以使用指向自身的指针的元素都是一个对象,因此它不需要存储自己的地址,因为它始终可以在方法内使用this来访问它。而且由于this指针并不存在 - 它是在调用时为调用方法的任何对象生成的 - 所以它不可能出错。 - underscore_d
1个回答

3

这可能会失败:

  • memcpy()仅适用于POD类型的向量。

  • 对于所有其他类型的对象,它都会失败,因为它不遵守对象拷贝(拷贝构造函数)的语义。

问题示例:

  • 如果对象的构造函数将一些内部指针设置为内部成员,则您的memcpy()将复制原始指针的值,该值将不会被正确更新并继续指向将被释放的内存区域。
  • 如果对象包含一个shared_ptr,那么对象计数将变得不一致(memcpy()将复制指针而不增加其引用计数,然后swap()将确保原始共享指针在b中,它将被释放,因此共享指针引用计数将被减少)。

正如T.C在评论中指出的那样,一旦您的向量存储非POD数据,memcpy()就会导致UB(未定义行为)。


2
对于非平凡可复制类型,这只是UB,没有生存对象在目标处。 - T.C.
1
当然是未定义行为!不过,我认为值得尝试向“memcpy”瘾君子解释一下,以及用“memcpy”参数来说明一个对象不仅仅是其内存总和。;-) - Christophe
4
好的,但值得强调的是,在一般情况下这是未定义行为,实现可以使你的猫怀孕,即使所涉及的类是 struct A { ~A() {} }; - T.C.
2
UB == 未定义行为。 - marko
1
POD太强了,顺便说一下。你只需要可平凡复制的。 - T.C.
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接