1. 目标
实现标准库vector类的一个简化版本,只支持string,我们命名为StrVec。
2. 设计思想
2.1 allocator管理每个StrVec对象的内存池, 是一块连续的内存(类型为元素的数组)。用于构建存储插入的元素
push_back插入元素, 检查内存池是否够用?
. 够用,在内存池的下—可用位置构造对象·
. 不够用,重组空间:
. allocator获取全新的更大的内存池
. 将已有元素拷贝至新的内存池
. 释放旧的内存池
. 在新内存池的下—可用位置构造新加入的元素
2.2 四个工具函数:
alloc_n_copy 会分配内存,并拷贝一个给定范围中的元素。
free 会销毁构造元素并释放内存。
chk_n_alloc 保证 StrVec 至少有容纳一个新元素的空间,如果没有空间添加新元素,chk_n_alloc 会调用 reallocate 类分配更多的内存。
reallocate 在内存用完时为 StrVec 分配新的内存。
3. StrVec.h
class StrVec {
public:
// copy control members
StrVec():
elements(nullptr), first_free(nullptr), cap(nullptr) {
}
StrVec(const StrVec&); // copy constructor
StrVec &operator=(const StrVec&); // copy assignment
#ifdef NOEXCEPT
StrVec(StrVec&&) noexcept; // move constructor
StrVec &operator=(StrVec&&) noexcept; // move assignment
~StrVec() noexcept; // destructor
#else
StrVec(StrVec&&) throw(); // move constructor
StrVec &operator=(StrVec&&) throw(); // move assignment
~StrVec() throw(); // destructor
#endif
#ifdef INIT_LIST
// additional constructor
StrVec(std::initializer_list<std::string>);
#else // define a constructor that takes pointers to a range of elements
StrVec(const std::string*, const std::string*);
#endif
void push_back(const std::string&); // copy the element
void push_back(std::string&&); // move the element
// add elements
size_t size() const {
return first_free - elements; }
size_t capacity() const {
return cap - elements; }
// iterator interface
std::string *begin() const {
return elements; }
std::string *end() const {
return first_free; }
#ifdef INIT_LIST // no real substitute for initializer_list in assignments
// operator functions covered in chapter 14
StrVec &operator=(std::initializer_list<std::string>);
#endif
std::string& operator[](std::size_t n)
{
return elements[n]; }
const std::string& operator[](std::size_t n) const
{
return elements[n]; }
#ifdef VARIADICS // no direct substitute for variadic functions
// emplace member covered in chapter 16
template <class... Args> void emplace_back(Args&&...);
#endif
private:
static std::allocator<std::string> alloc; // allocates the elements
// utility functions:
// used by members that add elements to the StrVec
void chk_n_alloc()
{
if (size() == capacity()) reallocate(); }
// used by the copy constructor, assignment operator, and destructor
std::pair<std::string*, std::string*> alloc_n_copy
(const std::string*, const std::string*);
void free(); // destroy the elements and free the space
void reallocate(); // get more space and copy the existing elements
std::string *elements; // pointer to the first element in the array
std::string *first_free; // pointer to the first free element in the array
std::string *cap; // pointer to one past the end of the array
};
#include <algorithm>
inline
#ifdef NOEXCEPT
StrVec::~StrVec() noexcept {
free(); }
#else
StrVec::~StrVec() throw() {
free(); }
#endif
4. StrVec.cpp
inline
std::pair<std::string*, std::string*>
StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
// allocate space to hold as many elements as are in the range
auto data = alloc.allocate(e - b);
// initialize and return a pair constructed from data and
// the value returned by uninitialized_copy
#ifdef LIST_INIT
return {
data, uninitialized_copy(b, e, data)};
#else
return make_pair(data, uninitialized_copy(b, e, data));
#endif
}
inline
#ifdef NOEXCEPT
StrVec::StrVec(StrVec &&s) noexcept // move won't throw any exceptions
#else
StrVec::StrVec(StrVec &&s) throw() // move won't throw any exceptions
#endif
// member initializers take over the resources in s
: elements(s.elements), first_free(s.first_free), cap(s.cap)
{
// leave s in a state in which it is safe to run the destructor
s.elements = s.first_free = s.cap = nullptr;
}
inline StrVec::StrVec(const StrVec &s)
{
// call alloc_n_copy to allocate exactly as many elements as in s
auto newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
inline void StrVec::free()
{
// may not pass deallocate a 0 pointer; if elements is 0, there's no work to do
if (elements) {
// destroy the old elements in reverse order
for (auto p = first_free; p != elements; /* empty */)
alloc.destroy(--p);
alloc.deallocate(elements, cap - elements);
}
}
#ifdef INIT_LIST
inline StrVec &StrVec::operator=(std::initializer_list<std::string> il)
{
// alloc_n_copy allocates space and copies elements from the given range
auto data = alloc_n_copy(il.begin(), il.end());
free(); // destroy the elements in this object and free the space
elements = data.first; // update data members to point to the new space
first_free = cap = data.second;
return *this;
}
#endif
inline
#ifdef NOEXCEPT
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
#else
StrVec &StrVec::operator=(StrVec &&rhs) throw()
#endif
{
// direct test for self-assignment
if (this != &rhs) {
free(); // free existing elements
elements = rhs.elements; // take over resources from rhs
first_free = rhs.first_free;
cap = rhs.cap;
// leave rhs in a destructible state
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
inline StrVec &StrVec::operator=(const StrVec &rhs)
{
// call alloc_n_copy to allocate exactly as many elements as in rhs
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
inline void StrVec::reallocate()
{
// we'll allocate space for twice as many elements as the current size
auto newcapacity = size() ? 2 * size() : 1;
// allocate new memory
auto newdata = alloc.allocate(newcapacity);
// move the data from the old memory to the new
auto dest = newdata; // points to the next free position in the new array
auto elem = elements; // points to the next element in the old array
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free(); // free the old space once we've moved the elements
// update our data structure to point to the new elements
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
#ifdef INIT_LIST
inline StrVec::StrVec(std::initializer_list<std::string> il)
{
// call alloc_n_copy to allocate exactly as many elements as in il
auto newdata = alloc_n_copy(il.begin(), il.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
#else
inline StrVec::StrVec(const std::string *b, const std::string* e)
{
// call alloc_n_copy to allocate exactly as many elements as in the range
auto newdata = alloc_n_copy(b, e);
elements = newdata.first;
first_free = cap = newdata.second;
}
#endif
inline void StrVec::push_back(const std::string& s)
{
chk_n_alloc(); // ensure that there is room for another element
// construct a copy of s in the element to which first_free points
alloc.construct(first_free++, s);
}
inline void StrVec::push_back(std::string &&s)
{
chk_n_alloc(); // reallocates the StrVec if necessary
alloc.construct(first_free++, std::move(s));
}
#ifdef VARIADICS // no direct substitute for variadic functions
// emplace member covered in chapter 16
template <class... Args>
inline void StrVec::emplace_back(Args&&... args)
{
chk_n_alloc(); // reallocates the StrVec if necessary
alloc.construct(first_free++, std::forward<Args>(args)...);
}
#endif
#endif
【参考】
[1] 代码StrVec.h