當我們分配一大塊內存時,我們通常計劃再這塊內存上按需構造對象,在次情況下,我們希望內存分配和對象構造分離。
內存分配和對象構造組合在一起可能會導致不必要的浪費,比如數組等,有時我們並不需這麼大的空間。
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》