使用memcpy的Vector的push_back实现

时间:2013-10-26 07:59:02

标签: c++

给出以下push_back代码:

template <typename T>
void Vector<T>::push_back(const T& item) {

if (_size == _capacity) {
    _capacity = _capacity + (_capacity > 1 ? (_capacity / 2) : 1);
    T* newVec = new T[_capacity];
    memcpy(newVec, _ptr, _size*(sizeof(T)));
    delete [] _ptr;
    _ptr = newVec;
}
_ptr[_size++] = item;
}

虽然vector的类包含这些成员:

T*  _ptr;
size_t _size;
size_t _capacity;

这种实施安全吗?即使T是多态类型,memcpy也会正确地完成他的工作吗?

很想听听有关如何改进实施的一些建议。

4 个答案:

答案 0 :(得分:7)

不要使用std::memcpy

您只能在简单的可复制对象上使用std::memcpy。否则它是未定义的行为。

但是,您可以手动复制所有元素。 std::copy是合适的,因为它可能专门用于琐碎的类型:

  

实际上,如果值类型为TriviallyCopyable,则std::copy的实现会避免多次分配并使用批量复制功能,例如std::memcpy

template <typename T>
void Vector<T>::push_back(const T& item) {
  if (_size == _capacity) {
      size_t new_cap = _capacity > 0 ? 2 * _capacity : 2;    
      T * newVec = new T[new_cap];
      std::copy(_ptr, _ptr + _size, newVec);
      std::swap(_capacity, new_cap);
      std::swap(_ptr, newVec);
      delete[] newVec;
  }
  _ptr[_size++] = item;
}

请注意,如果向量太小,原始实现会将容量分开。

更多改进

如果您使用std::allocator(或兼容类),事情会变得更容易一些。您可以使用.allocate获取内存,.construct(pointer, value)实际构造对象,.destroy调用其析构函数,.deallocate删除以前创建的内存.allocate。因此,如果您只想使用.push_back(),则不需要默认的可构造对象。

以下代码是一个快速最小草图。请注意,存在一些问题,例如reserve()不是异常安全的,因为如果构造函数抛出,则需要清除tmp中分配的内存。

template <typename T, class Allocator = std::allocator<T> >
class Vector{
public:
  typedef typename Allocator::pointer pointer;
  typedef typename Allocator::size_type size_type;

  Vector() : _ptr(0), _capacity(0), _size(0){}
  ~Vector() {
    if(_capacity == 0)
      return;
    while(_size > 0)
      pop_back();
    _alloc.deallocate(_ptr, _capacity);
  }

  void reserve(size_type new_cap){
    if(new_cap <= _capacity)
      return;

    // allocate memory
    T * tmp = _alloc.allocate(new_cap);

    // construct objects
    for(unsigned int i = 0; i < _size; ++i){
      _alloc.construct(tmp + i, _ptr[i]); // or std::move(_ptr[i])
    }

    // finished construction, save to delete old values
    for(unsigned int i = 0; i < _size; ++i){
      _alloc.destroy(_ptr + i);
    }

    // deallocate old memory
    _alloc.deallocate(_ptr, _capacity);
    _ptr = tmp;
    _capacity = new_cap;
  }

  void push_back(const T& val){
    if(_size == _capacity)
      reserve(_capacity > 0 ? 2 * _capacity : 1);    
    _alloc.construct(_ptr + _size, val);
    _size++; // since T::T(..) might throw
  }

  void pop_back(){
    _alloc.destroy(_ptr + _size - 1);
    _size--;    
  }

  T& operator[](size_type index){
    return _ptr[index];
  }

private:
  pointer _ptr;
  size_type _capacity;
  size_type _size;
  Allocator _alloc;
};

答案 1 :(得分:3)

这不安全,例如T正在执行此操作:

struct T
{
    T* myself;
    T() : myself(this) {}
    void foo() { myself->bar(); }
    void bar() { ... }
};

由于您通过简单地移动内存而不调用构造函数/析构函数来移动对象的内存位置,因此myself将不会更新,之后当您调用foo时,它将调用bar使用无效的this指针。

答案 2 :(得分:1)

想象一下如果T本身就是vector会发生什么。

现在你有两个指向相同缓冲区的向量,它们将两个删除缓冲区......糟糕的主意。

(嗯,从技术上来说,当你memcpy时,它就是未定义的行为。我只是给了你最可能的结果。)

答案 3 :(得分:0)

一般来说不安全 - 但C ++ 11提供了std::is_trivially_copyable

#include <type_traits>
...
if (std::is_trivially_copyable<T>::value)
    // *can* use memcpy...
相关问题