当我们分配一大块内存时,我们通常计划再这块内存上按需构造对象,在次情况下,我们希望内存分配和对象构造分离。
内存分配和对象构造组合在一起可能会导致不必要的浪费,比如数组等,有时我们并不需这么大的空间。
allocator
标准库allocator类定义在头文件memory中,它可以用来将内存分配和对象构造分离,它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。它也是一个模板对象,所以需要指明分配的类型。分配内存时它会根据给定的对象类型来决定恰当的内存大小和对齐位置。
方法名 | 作用 |
---|---|
alloctor<T> a | 声明一个类型为T的allocator对象 a |
a.allocate(n) | 分配一段原始的、未构造的内存,保存n个类型为T的对象 |
a.deallocate(p,n) | 释放指针p地址开始的内存,这块内存保存了n个类型为T的对象;注意:p必须是一个先前allocate返回的指针,且n是p创建时所要求的大小。调用deallocate前,必须对每个在这块内存上创建的对象调用destroy |
a.construct(p,args) | p必须是一个类型为T*的指针,指向一块原始内存;arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象 |
a.destroy(p) | p为T*类型的指针,此算法对p指向的对象执行析构函数 |
拷贝和填充未初始化内存的算法
方法名 | 作用 |
---|---|
uninitialized_copy(b,e,b2) | 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的内构造的原始内存中。b2指向的内存必须足够大,能容纳输入序列中的元素的拷贝 |
uninitialized_copy_n(b,n,b2) | 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中 |
uninitialized_fill(b,e,t) | 在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝 |
uninitialized_fill_n(b,n,t) | 从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象 |
注意copy返回的是目的位置迭代器。即最后一个元素之后的位置。
具体的使用可以参看下面这个类的设计
相当于只针对string类型的vector
#pragma once
#include <memory>
#include <string>
#include <utility>
class StrVec {
public:
typedef std::string* iterator;
StrVec():elements(nullptr), first_free(nullptr), cap(nullptr) {
}
StrVec(const StrVec&);
StrVec& operator = (const StrVec&);
~StrVec() {
free();
}
void push_back(const std::string);
size_t size()const {
return first_free - elements;
}
size_t capacity() {
return cap - elements;
}
std::string* begin()const {
return elements;
}
std::string* end()const {
return first_free;
}
private:
static std::allocator<std::string> alloc;
void check_n_alloc() {
if (size() == capacity())
reallocate();
}
std::pair<std::string*, std::string*> alloc_n_copy(const std::string*, const std::string*);
void free();
void reallocate();
std::string* elements;
std::string* first_free;
std::string* cap;
};
std::allocator<std::string> StrVec::alloc;//static类外初始化
void StrVec::push_back(const std::string s) {
check_n_alloc();
alloc.construct(first_free++, s);
}
std::pair<std::string*, std::string*> StrVec::alloc_n_copy(const std::string* b, const std::string* e) {
auto data = alloc.allocate(e - b);
return std::make_pair(data, uninitialized_copy(b, e, data));
}
void StrVec::free() {
if (elements) {
for (std::string* p = first_free; p != elements; alloc.destroy(--p));
//必须要先把已经构造的内存destroy
alloc.deallocate(elements, cap - elements);
}
}
StrVec::StrVec(const StrVec& s) {
std::pair<std::string*, std::string*> newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec& StrVec::operator=(const StrVec& s) {
std::pair<std::string*, std::string*> data = alloc_n_copy(s.begin(), s.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
void StrVec::reallocate() {
size_t newcapacity = size() ? 2 * size() : 1;
std::string* newdata = alloc.allocate(newcapacity);
std::string* dest = newdata;//指向新内存的起始位置
std::string* elem = elements;//指向旧内存的起始位置
/*把旧内存中的数据std::move到新内存中*/
for (size_t i = 0; i != size(); ++i) {
alloc.construct(dest++, std::move(*elem++));
}
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
- 参看书籍 《C++ primer》