给出以下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也会正确地完成他的工作吗?
很想听听有关如何改进实施的一些建议。
答案 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...