1. Goal
Implement a simplified version of the standard library vector class, which only supports string, we named it StrVec.
2. Design Ideas
2.1 The allocator manages the memory pool of each StrVec object, which is a contiguous memory (an array of types). Elements used to build storage inserts
push_back插入元素, 检查内存池是否够用?
. 够用,在内存池的下—可用位置构造对象·
. 不够用,重组空间:
. allocator获取全新的更大的内存池
. 将已有元素拷贝至新的内存池
. 释放旧的内存池
. 在新内存池的下—可用位置构造新加入的元素
2.2 Four tool functions:
alloc_n_copy will allocate memory and copy the elements in a given range.
free will destroy the constructed element and release the memory.
chk_n_alloc guarantees that StrVec has space for at least one new element. If there is no space to add a new element, chk_n_alloc will call the reallocate class to allocate more memory.
reallocate allocates new memory for StrVec when the memory is used up.
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
【reference】
[1] Code StrVec.h