上一篇文章里面讲到用allocator预分配空间已经在需要的时候才构造对象,其实还有其它的办法实现这一功能。operator new表达式也可以只分配内存而不初始化,但是返回的是void指针,因此没有allocator类型化的优点。同样placement new表达式只负责调用指定的构造函数初始化内存,不申请内存,与allocator的construct相比,这个表达式更加方便,可以使用任意类型的构造函数参数列表。但是,construct只能使用复制构造函数。
同样的,可以使用operator delete代替allocator的deallocate释放内存。使用析构函数代替allocator的destroy清理对象。下面我把原来使用allocator的代码改写成使用这些表达式的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <memory> #include <algorithm>
template <typename T> class Vector { public: Vector() : elements(0), first_free(0), end(0) {} ~Vector() { for (T* p = first_free; p != elements;) { --p; p->~T(); }
if (elements) { operator delete[](elements); } }
void push_back(const T t) { if (first_free == end) { reallocate(); }
new (first_free) T(t); ++first_free; }
void pop_back() { if (first_free != elements) { --first_free; first_free->~T(); } }
private: void reallocate() { std::ptrdiff_t size = first_free - elements; std::ptrdiff_t newcapacity = 2 * std::max(size, 1);
T* newelements = static_cast<T*>(operator new[](newcapacity * sizeof(T)));
std::uninitialized_copy(elements, first_free, newelements);
for (T* p = first_free; p != elements;) { --p; p->~T(); }
if (elements) { operator delete[](elements); }
elements = newelements; first_free = elements + size; end = elements + newcapacity; }
T* elements; T* first_free; T* end; };
|
placement new表达式的一般形式是new (地址) 类型名(初始化列表),由于使用初始化列表直接构造对象,因此不用拷贝。而且比只能用复制构造函数构造的allocator的construct函数更方便。
比如,allocator
alloc;string* sp = alloc.allocate(2);如果用定位new表达式,new (sp) string(b, e);(b,e是用2个迭代器构造string类型)。但是,用construct的话,则是alloc.construct(sp+1,string(b,e));可以清楚的看到,需要用迭代器构造个临时的string对象,再用这个临时对象调用复制构造函数。因此,会有细微的性能损失。最终要的是,有些类根本没有可以调用的复制构造函数,比如该函数是私有的,那么就必须使用placement new表达式了。
operator new和operator delete的使用方式类似于new和delete,具体的可以参看代码。