C++ Primer 5th笔记(chap 13 拷贝控制) 实例2内存管理

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

猜你喜欢

转载自blog.csdn.net/thefist11cc/article/details/113914384