├── CMakeLists.txt ├── easySTL ├── algo.h ├── allocator.h ├── constructor.h ├── iterator.h ├── uninitialized.h └── vector.h ├── test ├── CMakeLists.txt ├── test.cpp ├── test.h └── vectortest.h ├── tutorial1.md ├── tutorial2.md ├── tutorial3.md └── tutorial4.md /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | project(easySTL) 3 | add_subdirectory(${PROJECT_SOURCE_DIR}/test) 4 | -------------------------------------------------------------------------------- /easySTL/algo.h: -------------------------------------------------------------------------------- 1 | #ifndef EASYSTL_ALGO_H_ 2 | #define EASYSTL_ALGO_H_ 3 | 4 | namespace easystl { 5 | 6 | template 7 | inline const T& Max(const T& a, const T& b) noexcept { return a > b ? a : b; } 8 | 9 | template 10 | OutputIterator Copy(InputIterator first, InputIterator last, OutputIterator result) noexcept { 11 | while (first != last) { 12 | *result = *first; 13 | ++result; 14 | ++first; 15 | } 16 | return result; 17 | } 18 | 19 | template 20 | BidirectionalIterator2 Copybackward(BidirectionalIterator1 first, BidirectionalIterator1 last, BidirectionalIterator2 result) noexcept { 21 | while (first != last) { 22 | *(--result) = *(--last); 23 | } 24 | return result; 25 | } 26 | 27 | template 28 | void Fill(ForwardIterator first, ForwardIterator last, const T& value) noexcept { 29 | while (first != last) { 30 | *first = value; 31 | ++first; 32 | } 33 | } 34 | 35 | template 36 | void Swap(T& a, T& b) noexcept { 37 | T temp = a; 38 | a = b; 39 | b = temp; 40 | } 41 | 42 | } // namespace easystl 43 | 44 | #endif // EASYSTL_ALGO_H_ -------------------------------------------------------------------------------- /easySTL/allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef EASYSTL_ALLOCATOR_H_ 2 | #define EASYSTL_ALLOCATOR_H_ 3 | 4 | #include 5 | 6 | namespace easystl { 7 | // malloc allocator 8 | // directly use malloc and free to manage memory 9 | class MallocAllocator { 10 | public: 11 | static void* Allocate(size_t size) { 12 | void *result = malloc(size); 13 | if(0==result) { result = MallocInOom(size); } 14 | return result; 15 | } 16 | static void Deallocate(void *obj, size_t /*size*/) { 17 | free(obj); 18 | } 19 | static void* Reallocate(void *obj, size_t /*oldsize*/, size_t newsize) { 20 | void *result = realloc(obj, newsize); 21 | if(0==result) { result = ReallocInOom(obj, newsize); } 22 | return result; 23 | } 24 | // simulate set_new_handler in C++ 25 | // customer can call this func to set 26 | // their own out-of-memory handler 27 | static void (*SetMallocHandler(void (*func)()))() { 28 | void (*old)() = CustomerOomHandler; 29 | CustomerOomHandler = func; 30 | return old; 31 | } 32 | 33 | private: 34 | // funcs to be called when out of memory 35 | static void* MallocInOom(size_t size); 36 | static void* ReallocInOom(void *obj, size_t size); 37 | static void (*CustomerOomHandler)(); 38 | }; 39 | 40 | void (*MallocAllocator::CustomerOomHandler)() = nullptr; 41 | 42 | void* MallocAllocator::MallocInOom(size_t size) { 43 | void (*my_malloc_handler)(); 44 | void *result; 45 | while(true) { 46 | my_malloc_handler = CustomerOomHandler; 47 | // if CustomerOomHandler do not be setted 48 | // just exit 49 | if(nullptr==my_malloc_handler) { 50 | std::abort(); 51 | }; 52 | (*my_malloc_handler)(); 53 | result = malloc(size); 54 | if(result) { return result; } 55 | } 56 | } 57 | 58 | void* MallocAllocator::ReallocInOom(void *obj, size_t size) { 59 | void (*my_malloc_handler)(); 60 | void *result; 61 | while(true) { 62 | my_malloc_handler = CustomerOomHandler; 63 | // same as MallocInOom 64 | if(nullptr==my_malloc_handler) { 65 | std::abort(); 66 | }; 67 | (*my_malloc_handler)(); 68 | result = realloc(obj, size); 69 | if(result) { return result; } 70 | } 71 | } 72 | 73 | // memory pool class 74 | // in order to save space 75 | // every node will use itself 76 | // to be the pointer to the next 77 | class MemoryPoolList { 78 | public: 79 | bool Empty() const { return node_ == nullptr; } 80 | void*& GetNextNode(void *node) { return *static_cast(node); } 81 | void Push(void *node) { 82 | GetNextNode(node) = node_; 83 | node_ = node; 84 | } 85 | void* Pop() { 86 | void *result = node_; 87 | node_ = GetNextNode(result); 88 | return result; 89 | } 90 | 91 | private: 92 | void * node_ = nullptr; 93 | }; 94 | 95 | // constexpr value 96 | static constexpr int kAlign = 8; 97 | static constexpr int kMaxBytes = 128; 98 | static constexpr int kFreeListNum = kMaxBytes/kAlign; 99 | 100 | // memory pool allocator 101 | class MemoryPoolAllocator { 102 | public: 103 | static void* Allocate(size_t size) { 104 | if(size > size_t(kMaxBytes)) { 105 | return MallocAllocator::Allocate(size); 106 | } 107 | size_t index = GetFreelistIndex(size); 108 | if (freelist_[index].Empty()) { 109 | return ReFill(RoundUp(size)); 110 | } 111 | else { 112 | return freelist_[index].Pop(); 113 | } 114 | } 115 | static void Deallocate(void *obj, size_t size) { 116 | if(size>kMaxBytes) { 117 | MallocAllocator::Deallocate(obj, size); 118 | return ; 119 | } 120 | freelist_[GetFreelistIndex(size)].Push(obj); 121 | } 122 | static void* Reallocate(void *obj, size_t oldsize, size_t newsize) { 123 | if (RoundUp(newsize) == RoundUp(oldsize)) { 124 | return obj; // No need to reallocate if sizes are equal 125 | } 126 | Deallocate(obj, oldsize); // Free the old memory 127 | return Allocate(newsize); // Allocate new memory 128 | } 129 | 130 | private: 131 | // align size to multiples of 8 132 | static size_t RoundUp(size_t bytes) { return ((bytes + kAlign - 1)&~(kAlign - 1)); } 133 | static size_t GetFreelistIndex(size_t bytes) { return ((bytes + kAlign - 1)/kAlign -1); } 134 | // too big, define outside 135 | // refill freespace 136 | static void* ReFill(size_t size); 137 | // get new chunk to freespace 138 | static char* ChunkAlloc(size_t size, size_t &chunknums); 139 | 140 | static MemoryPoolList freelist_[kFreeListNum]; 141 | static char *freespacestart_; 142 | static char *freespaceend_; 143 | static size_t mallocoffset_; 144 | }; 145 | 146 | char * MemoryPoolAllocator::freespacestart_ = nullptr; 147 | char * MemoryPoolAllocator::freespaceend_ = nullptr; 148 | size_t MemoryPoolAllocator::mallocoffset_ = 0; 149 | MemoryPoolList MemoryPoolAllocator::freelist_[kFreeListNum]; 150 | 151 | char* MemoryPoolAllocator::ChunkAlloc(size_t size, size_t &chunknums) { 152 | char *result; 153 | size_t bytesneed = size * chunknums; 154 | size_t bytesleft = freespaceend_ - freespacestart_; 155 | if (bytesleft >= bytesneed) { 156 | result = freespacestart_; 157 | freespacestart_ += bytesneed; 158 | return result; 159 | } 160 | else if (bytesleft >= size) { 161 | chunknums = bytesleft/size; 162 | bytesneed = chunknums * size; 163 | result = freespacestart_; 164 | freespacestart_ += bytesneed; 165 | return result; 166 | } 167 | else { 168 | size_t bytesget = 2 * bytesneed + RoundUp(mallocoffset_ >> 4); 169 | if(bytesleft >= kAlign) { 170 | freelist_[GetFreelistIndex(bytesleft)].Push(freespacestart_); 171 | } 172 | freespacestart_ = (char *)malloc(bytesget); 173 | if (freespacestart_ == nullptr) { 174 | for (size_t i = size; i <= kMaxBytes; i += kAlign) { 175 | if(!freelist_[GetFreelistIndex(i)].Empty()) { 176 | freespacestart_ = (char*)freelist_[GetFreelistIndex(i)].Pop(); 177 | freespaceend_ = freespacestart_ + i; 178 | return ChunkAlloc(size, chunknums); 179 | } 180 | } 181 | freespaceend_ = nullptr; 182 | freespacestart_ = (char *)MallocAllocator::Allocate(bytesget); 183 | } 184 | mallocoffset_ += bytesget; 185 | freespaceend_ = freespacestart_ + bytesget; 186 | return ChunkAlloc(size, chunknums); 187 | } 188 | } 189 | 190 | void* MemoryPoolAllocator::ReFill(size_t size) { 191 | size_t chunknums = 20; 192 | char *chunk = ChunkAlloc(size, chunknums); 193 | char *nextchunk = chunk + size; 194 | if(chunknums == 1) { 195 | return chunk; 196 | } 197 | for (int i = 1; i < chunknums; ++i) { 198 | freelist_[GetFreelistIndex(size)].Push(nextchunk); 199 | nextchunk += size; 200 | } 201 | return chunk; 202 | } 203 | 204 | #define USEMALLOC 205 | #ifdef USEMALLOC 206 | using Allo = MemoryPoolAllocator; 207 | #else 208 | using Allo = MallocAllocator; 209 | #endif 210 | 211 | template 212 | class AllocatorWrapper { 213 | public: 214 | static T * Allocate(size_t n) { return 0 == n ? 0 : (T*)Allocator::Allocate(n*sizeof(T)); } 215 | static T * Allocate(void) { return (T*)Allocator::Allocate(sizeof(T)); } 216 | static void Deallocate(T * p, size_t n) { if(0 != n) { Allocator::Deallocate(p, n*sizeof(T)); } } 217 | static void Deallocate(T * p) { Allocator::Deallocate(p, sizeof(T)); } 218 | }; 219 | 220 | } // namespace easystl 221 | 222 | #endif // EASYSTL_ALLOCATOR_H_ -------------------------------------------------------------------------------- /easySTL/constructor.h: -------------------------------------------------------------------------------- 1 | #ifndef EASYSTL_CONSTRUCTOR_H_ 2 | #define EASYSTL_CONSTRUCTOR_H_ 3 | 4 | #include 5 | 6 | namespace easystl { 7 | 8 | template 9 | inline void Construct(T* p) { new(p) T(); } 10 | 11 | template 12 | inline void Construct(T1* p, const T2 & value) { new(p) T1(value); } 13 | 14 | template 15 | inline void Destroy(T* p) { p->~T(); } 16 | 17 | template 18 | inline void Destroy(ForwardIterator first, ForwardIterator last) { 19 | for(; first != last; ++first) { 20 | Destroy(&*first); 21 | } 22 | } 23 | 24 | } // namespace easystl 25 | 26 | #endif // EASYSTL_CONSTRUCTOR_H_ -------------------------------------------------------------------------------- /easySTL/iterator.h: -------------------------------------------------------------------------------- 1 | #ifndef EASYSTL_ITERATOR_H_ 2 | #define EASYSTL_ITERATOR_H_ 3 | 4 | #include 5 | 6 | namespace easystl { 7 | 8 | class TrueType { 9 | public: 10 | static const bool value = true; 11 | }; 12 | 13 | class FalseType { 14 | public: 15 | static const bool value = false; 16 | }; 17 | 18 | template using void_t = void; 19 | 20 | class InputIteratorTag {}; 21 | class OutputIteratorTag {}; 22 | class ForwardIteratorTag : public InputIteratorTag {}; 23 | class BidirectionalIteratorTag : public ForwardIteratorTag {}; 24 | class RandomAccessIteratorTag : public BidirectionalIteratorTag {}; 25 | 26 | template 31 | class Iterator { 32 | public: 33 | using IteratorCategory = Category; 34 | using ValueType = T; 35 | using Pointer = pointer; 36 | using Reference = reference; 37 | using DifferenceType = distance; 38 | }; 39 | 40 | template 41 | class IteratorTraits {}; 42 | 43 | template 44 | class IteratorTraits> { 45 | public: 46 | using IteratorCategory = typename T::IteratorCategory; 47 | using ValueType = typename T::ValueType; 48 | using Pointer = typename T::Pointer; 49 | using Reference = typename T::Reference; 50 | using DifferenceType = typename T::DifferenceType; 51 | }; 52 | 53 | template 54 | class IteratorTraits { 55 | public: 56 | using IteratorCategory = RandomAccessIteratorTag; 57 | using ValueType = T; 58 | using Pointer = T*; 59 | using Reference = T&; 60 | using DifferenceType = ptrdiff_t; 61 | }; 62 | 63 | template 64 | class IteratorTraits { 65 | public: 66 | using IteratorCategory = RandomAccessIteratorTag; 67 | using ValueType = T; 68 | using Pointer = const T*; 69 | using Reference = const T&; 70 | using DifferenceType = ptrdiff_t; 71 | }; 72 | 73 | template 74 | class IsIterator { 75 | private: 76 | template 77 | static FalseType test(...); 78 | template 79 | static auto test(int) -> decltype(typename IteratorTraits::IteratorCategory(), TrueType()); 80 | public: 81 | static const bool value = decltype(test(0))::value; 82 | }; 83 | 84 | template 85 | inline typename IteratorTraits::IteratorCategory 86 | IteratorCategory(const Iterator&) { 87 | typedef typename IteratorTraits::IteratorCategory Category; 88 | return Category(); 89 | } 90 | 91 | template 92 | inline typename IteratorTraits::DifferenceType * 93 | DistanceType(const Iterator&) { 94 | return static_cast::DifferenceType *>(0); 95 | } 96 | 97 | template 98 | inline typename IteratorTraits::ValueType * 99 | ValueType(const Iterator&) { 100 | return static_cast::ValueType *>(0); 101 | } 102 | 103 | template 104 | inline typename IteratorTraits::DifferenceType 105 | __Distance(InputIterator first, InputIterator last, InputIteratorTag) { 106 | typename IteratorTraits::DifferenceType n = 0; 107 | while(first!=last) { 108 | ++first; 109 | ++n; 110 | } 111 | return n; 112 | } 113 | 114 | template 115 | inline typename IteratorTraits::DifferenceType 116 | __Distance(RandomAccessIterator first, RandomAccessIterator last, RandomAccessIteratorTag) { 117 | return last - first; 118 | } 119 | 120 | template 121 | inline typename IteratorTraits::DifferenceType 122 | Distance(InputIterator first, InputIterator last) { 123 | return __Distance(first, last, IteratorCategory(first)); 124 | } 125 | 126 | template 127 | inline void __Advance(InputIterator &i, Distance n, InputIteratorTag) { 128 | while (n--) { ++i; } 129 | } 130 | 131 | template 132 | inline void __Advance(BidirectionalIterator &i, Distance n, BidirectionalIteratorTag) { 133 | if(n > 0) { 134 | while(n--) { ++i; } 135 | } 136 | else { 137 | while(n++) { --i; } 138 | } 139 | } 140 | 141 | template 142 | inline void __Advance(RandomAccessIterator &i, Distance n, RandomAccessIteratorTag) { 143 | i += n; 144 | } 145 | 146 | template 147 | inline void Advance(InputIterator &i, Distance n) { 148 | __Advance(i, n, IteratorCategory(i)); 149 | } 150 | 151 | } // namespace easystl 152 | 153 | #endif // EASYSTL_ITERATOR_H_ -------------------------------------------------------------------------------- /easySTL/uninitialized.h: -------------------------------------------------------------------------------- 1 | #ifndef EASYSTL_UNINITIALIZED_H_ 2 | #define EASYSTL_UNINITIALIZED_H_ 3 | 4 | #include 5 | #include "constructor.h" 6 | // helper funcs to construct value in uninitialized place which is already allocated 7 | namespace easystl { 8 | 9 | template 10 | ForwardIter uninitialized_copy(InputIter first, InputIter last, ForwardIter result) { 11 | auto current = result; 12 | try { 13 | for(; first != last; ++first, ++current) { 14 | easystl::Construct(&*current, *first); 15 | } 16 | } 17 | catch(...) { 18 | easystl::Destroy(result, current); 19 | std::abort(); 20 | } 21 | return current; 22 | } 23 | 24 | template 25 | void uninitialized_fill(ForwardIter first, ForwardIter last, const T& value) { 26 | auto current = first; 27 | try { 28 | for(; current != last; ++current) { 29 | easystl::Construct(&*current, value); 30 | } 31 | } 32 | catch(...) { 33 | easystl::Destroy(first, current); 34 | std::abort(); 35 | } 36 | } 37 | 38 | template 39 | ForwardIter uninitialized_fill_n(ForwardIter first, Size n, const T& value) { 40 | auto current = first; 41 | try { 42 | for(; n > 0; --n, ++current) { 43 | easystl::Construct(&*current, value); 44 | } 45 | } 46 | catch(...) { 47 | easystl::Destroy(first, current); 48 | std::abort(); 49 | } 50 | return current; 51 | } 52 | 53 | } // namespace easystl 54 | 55 | #endif // EASYSTL_UNINITIALIZED_H_ -------------------------------------------------------------------------------- /easySTL/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef EASYSTL_VECTOR_H_ 2 | #define EASYSTL_VECTOR_H_ 3 | 4 | #include "allocator.h" 5 | #include "constructor.h" 6 | #include "iterator.h" 7 | #include "uninitialized.h" 8 | #include "algo.h" 9 | 10 | namespace easystl { 11 | 12 | template 13 | class vector { 14 | public: 15 | // type alias 16 | using value_type = T; 17 | using pointer = T*; 18 | using iterator = T*; 19 | using reference = T&; 20 | using size_type = size_t; 21 | using difference_type = ptrdiff_t; 22 | // constructor 23 | vector() noexcept : begin_(nullptr), end_(nullptr), capacity_(nullptr) {} 24 | vector(size_type len, const T& value) noexcept { NumsInit(len, value); } 25 | explicit vector(size_type len) noexcept { NumsInit(len, T()); } 26 | template::value, int> = 0> 27 | vector(Iterator first, Iterator last) noexcept { 28 | RangeInit(first, last); 29 | } 30 | 31 | vector(std::initializer_list ilist) { 32 | RangeInit(ilist.begin(), ilist.end()); 33 | } 34 | // copy constructor 35 | vector(const vector& other) noexcept { 36 | RangeInit(other.begin_, other.end_); 37 | } 38 | // copy assignment operator 39 | vector& operator=(const vector& rhs) noexcept { 40 | if(this != &rhs) { 41 | const size_type rhslen = rhs.size(); 42 | if(rhslen > capacity()) { 43 | vector tmp(rhs); 44 | swap(tmp); 45 | } 46 | else if(size() >= rhslen) { 47 | auto i = Copy(rhs.begin_, rhs.end_, begin_); 48 | Destroy(i, end_); 49 | end_ = begin_ + rhslen; 50 | } 51 | else { 52 | Copy(rhs.begin_, rhs.end_, begin_); 53 | uninitialized_copy(rhs.begin_ + size(), rhs.end_, end_); 54 | end_ = begin_ + rhslen; 55 | } 56 | } 57 | return *this; 58 | } 59 | vector& operator=(std::initializer_list ilist) noexcept { 60 | vector tmp(ilist); 61 | swap(tmp); 62 | return *this; 63 | } 64 | // destructor 65 | ~vector() noexcept { DestroynDeallocate(begin_, end_, static_cast(capacity_ - begin_)); } 66 | // basic operation 67 | iterator begin() noexcept { return begin_; } 68 | iterator end() noexcept { return end_; } 69 | size_type size() const noexcept { return static_cast(end_ - begin_); } 70 | bool empty() const noexcept { return begin_ == end_; } 71 | size_type capacity() const noexcept { return static_cast(capacity_ - begin_); } 72 | reference operator[] (size_type n) noexcept { return *(begin_ + n); } 73 | reference front() noexcept { return *begin(); } 74 | reference back()noexcept { return *(end_ - 1); } 75 | void push_back(const T& x) noexcept { 76 | if(end_ != capacity_) { 77 | Construct(end_, x); 78 | ++end_; 79 | } 80 | else { 81 | InsertAux(end_, 1, x); 82 | } 83 | } 84 | void pop_back() noexcept { 85 | if(empty()) { return; } 86 | --end_; 87 | Destroy(end_); 88 | } 89 | iterator erase(iterator pos) noexcept { 90 | Copy(pos + 1, end_, pos); 91 | Destroy(end_); 92 | --end_; 93 | return pos; 94 | } 95 | iterator erase(iterator first, iterator last) noexcept { 96 | if(first!=last) { 97 | auto i = Copy(last, end_, first); 98 | Destroy(i, end_); 99 | end_ = end_ - static_cast(last - first); 100 | } 101 | return last; 102 | } 103 | void resize(size_type newsize, const T& x) noexcept { 104 | if(newsize < size()) { 105 | erase(begin_ + newsize, end_); 106 | } 107 | else { 108 | insert(end_, newsize - size(), x); 109 | } 110 | } 111 | void resize(size_type newsize) noexcept { resize(newsize, T()); } 112 | void clear() noexcept { erase(begin_, end_); } 113 | pointer data() noexcept { return begin_; } 114 | // swap vector 115 | void swap(vector& rhs) noexcept { 116 | if(this != &rhs) { 117 | easystl::Swap(begin_, rhs.begin_); 118 | easystl::Swap(end_, rhs.end_); 119 | easystl::Swap(capacity_, rhs.capacity_); 120 | } 121 | } 122 | // insert 123 | iterator insert(iterator pos, size_type size, const T& x) noexcept { 124 | // leftbytes enough 125 | if(size_type(capacity_ - end_) >= size) { 126 | const size_type elemsafter = end_ - pos; 127 | iterator oldend = end_; 128 | if(elemsafter > size) { 129 | uninitialized_copy(end_ - size, end_, end_); 130 | end_ += size; 131 | Copybackward(pos, oldend - size, oldend); 132 | Fill(pos, pos + size, x); 133 | } 134 | else { 135 | uninitialized_fill_n(end_, size - elemsafter, x); 136 | end_ += size - elemsafter; 137 | uninitialized_copy(pos, oldend, end_); 138 | end_ += elemsafter; 139 | Fill(pos, oldend, x); 140 | } 141 | } 142 | // leftbytes not enough 143 | else { 144 | InsertAux(pos, size, x); 145 | } 146 | return pos; 147 | } 148 | 149 | template::value, int> = 0, 151 | typename std::enable_if_t::value, int> = 0> 152 | iterator insert(Iter1 pos, Iter2 first, Iter2 last) noexcept { 153 | if (first == last) return pos; 154 | const auto size = last - first; 155 | // leftbytes enough 156 | if (size_type(capacity_ - end_) >= size) { 157 | const size_type elemsafter = end_ - pos; 158 | iterator oldend = end_; 159 | if (elemsafter > size) { 160 | uninitialized_copy(end_ - size, end_, end_); 161 | end_ += size; 162 | Copybackward(pos, oldend - size, oldend); 163 | Copy(first, last, pos); 164 | } 165 | else { 166 | uninitialized_fill_n(end_, size - elemsafter, T()); 167 | end_ += size - elemsafter; 168 | uninitialized_copy(pos, oldend, end_); 169 | end_ += elemsafter; 170 | Copy(first, last, pos); 171 | } 172 | } 173 | // leftbytes not enough 174 | else { 175 | InsertAux(pos, first, last); 176 | } 177 | } 178 | iterator insert(iterator pos, const T& x) noexcept { 179 | return insert(pos, 1, x); 180 | } 181 | // assign 182 | void assign(size_type n, const T& value) noexcept { 183 | clear(); 184 | if (n > capacity()) { 185 | vector tmp(n, value); 186 | swap(tmp); 187 | } 188 | else { 189 | uninitialized_fill_n(begin_, n, value); 190 | end_ = begin_ + n; 191 | } 192 | } 193 | template::value, int> = 0> 194 | void assign(Iter first, Iter last) noexcept { 195 | clear(); 196 | const size_type len = last - first; 197 | if (len > capacity()) { 198 | vector tmp(first, last); 199 | swap(tmp); 200 | } 201 | else { 202 | uninitialized_copy(first, last, begin_); 203 | end_ = begin_ + len; 204 | } 205 | } 206 | void assign(std::initializer_list ilist) noexcept { 207 | assign(ilist.begin(), ilist.end()); 208 | } 209 | private: 210 | // allocator 211 | using DataAllocator = AllocatorWrapper; 212 | // allocate memory and construct 213 | // memory size is n * sizeof(T) 214 | // construct n variables of type T with values of value 215 | // initialize 216 | void NumsInit(size_type n, const T& value) noexcept { 217 | size_type initsize = easystl::Max(static_cast(n), static_cast(16)); 218 | iterator current = DataAllocator::Allocate(initsize); 219 | begin_ = current; 220 | end_ = easystl::uninitialized_fill_n(current, n, value); 221 | capacity_ = begin_ + initsize; 222 | } 223 | // range init 224 | template 225 | void RangeInit(Iter first, Iter last) noexcept { 226 | size_type initsize = easystl::Max(static_cast(last - first), static_cast(16)); 227 | begin_ = DataAllocator::Allocate(initsize); 228 | end_ = easystl::uninitialized_copy(first, last, begin_); 229 | capacity_ = begin_ + initsize; 230 | } 231 | // destroy and Deallocate 232 | void DestroynDeallocate(iterator first, iterator last, size_type len) noexcept { 233 | if(first) { 234 | Destroy(first, last); 235 | DataAllocator::Deallocate(first, len); 236 | } 237 | } 238 | // inesert when space not enough 239 | void InsertAux(iterator pos, size_type nums, const T& x) noexcept { 240 | const size_type newsize = Max(size()*2, static_cast(16)); 241 | iterator newbegin = DataAllocator::Allocate(newsize); 242 | iterator newend = newbegin; 243 | newend = uninitialized_copy(begin_, pos, newbegin); 244 | newend = uninitialized_fill_n(newend, nums, x); 245 | newend = uninitialized_copy(pos, end_, newend); 246 | DestroynDeallocate(begin_, end_, static_cast(capacity_ - begin_)); 247 | begin_ = newbegin; 248 | end_ = newend; 249 | capacity_ = begin_ + newsize; 250 | } 251 | template::value, int> = 0, 253 | typename std::enable_if_t::value, int> = 0> 254 | void InsertAux(Iter1 pos, Iter2 first, Iter2 last) noexcept { 255 | { 256 | const size_type newsize = Max(size() * 2, static_cast(16)); 257 | iterator newbegin = DataAllocator::Allocate(newsize); 258 | iterator newend = newbegin; 259 | newend = uninitialized_copy(begin_, pos, newbegin); 260 | newend = uninitialized_copy(first, last, newend); 261 | newend = uninitialized_copy(pos, end_, newend); 262 | DestroynDeallocate(begin_, end_, static_cast(capacity_ - begin_)); 263 | begin_ = newbegin; 264 | end_ = newend; 265 | capacity_ = begin_ + newsize; 266 | } 267 | } 268 | 269 | // flag 270 | iterator begin_; // flag for used memory head 271 | iterator end_; // flag for used memory tail 272 | iterator capacity_; // flag for available memory tail 273 | }; 274 | 275 | } // namespace easystl 276 | 277 | #endif // EASYSTL_VECTOR_H_ 278 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${PROJECT_SOURCE_DIR}/easySTL) 2 | set(APP_SRC test.cpp) 3 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) 4 | add_executable(stltest ${APP_SRC}) -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "vector.h" 3 | #include "vectortest.h" 4 | 5 | int main() 6 | { 7 | VectorTest(); 8 | } 9 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | #ifndef MYTINYSTL_TEST_H_ 2 | #define MYTINYSTL_TEST_H_ 3 | 4 | #include 5 | #include 6 | 7 | // output container 8 | #define COUT(container) do { \ 9 | std::string con_name = #container; \ 10 | std::cout << " " << con_name << " :"; \ 11 | for (auto it : container) \ 12 | std::cout << " " << it; \ 13 | std::cout << "\n"; \ 14 | } while(0) 15 | 16 | // output container after function called 17 | #define FUN_AFTER(con, fun) do { \ 18 | std::string fun_name = #fun; \ 19 | std::cout << " After " << fun_name << " :\n"; \ 20 | fun; \ 21 | COUT(con); \ 22 | } while(0) 23 | 24 | // output value after function called 25 | #define FUN_VALUE(fun) do { \ 26 | std::string fun_name = #fun; \ 27 | std::cout << " " << fun_name << " : " << fun << "\n"; \ 28 | } while(0) 29 | 30 | #endif // !MYTINYSTL_TEST_H_ 31 | 32 | -------------------------------------------------------------------------------- /test/vectortest.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test.h" 4 | #include "vector.h" 5 | 6 | void VectorTest() 7 | { 8 | std::cout << "[----------------- vector test -----------------]\n"; 9 | int a[] = { 1,2,3,4,5 }; 10 | easystl::vector v1; 11 | easystl::vector v2(10); 12 | easystl::vector v3(10, 1); 13 | easystl::vector v4(a + 0, a + 5); 14 | easystl::vector v5(v2); 15 | easystl::vector v6(v2); 16 | easystl::vector v7{ 1,2,3,4,5,6,7,8,9 }; 17 | easystl::vector v8, v9, v10; 18 | v8 = v3; 19 | v9 = v3; 20 | v10 = { 1,2,3,4,5,6,7,8,9 }; 21 | 22 | FUN_AFTER(v1, v1.assign(8, 8)); 23 | FUN_AFTER(v1, v1.assign(a, a + 5)); 24 | FUN_AFTER(v1, v1.push_back(6)); 25 | FUN_AFTER(v1, v1.insert(v1.end(), 7)); 26 | FUN_AFTER(v1, v1.insert(v1.begin() + 3, 2, 3)); 27 | FUN_AFTER(v1, v1.insert(v1.begin(), a + 0, a + 5)); 28 | FUN_AFTER(v1, v1.pop_back()); 29 | FUN_AFTER(v1, v1.erase(v1.begin())); 30 | FUN_AFTER(v1, v1.erase(v1.begin(), v1.begin() + 2)); 31 | FUN_AFTER(v1, v1.swap(v9)); 32 | FUN_VALUE(v1.size()); 33 | FUN_VALUE(v1.capacity()); 34 | FUN_VALUE(*v1.begin()); 35 | FUN_VALUE(*(v1.end() - 1)); 36 | FUN_VALUE(v1.front()); 37 | FUN_VALUE(v1.back()); 38 | FUN_VALUE(v1[0]); 39 | int* p = v1.data(); 40 | *p = 10; 41 | *++p = 20; 42 | p[1] = 30; 43 | std::cout << " After change v1.data() :" << "\n"; 44 | COUT(v1); 45 | std::cout << std::boolalpha; 46 | FUN_VALUE(v1.empty()); 47 | std::cout << std::noboolalpha; 48 | FUN_VALUE(v1.size()); 49 | FUN_VALUE(v1.capacity()); 50 | FUN_AFTER(v1, v1.resize(10)); 51 | FUN_VALUE(v1.size()); 52 | FUN_VALUE(v1.capacity()); 53 | FUN_AFTER(v1, v1.resize(6, 6)); 54 | FUN_VALUE(v1.size()); 55 | FUN_VALUE(v1.capacity()); 56 | FUN_AFTER(v1, v1.clear()); 57 | FUN_VALUE(v1.size()); 58 | FUN_VALUE(v1.capacity()); 59 | std::cout << "[----------------- End -----------------]\n"; 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /tutorial1.md: -------------------------------------------------------------------------------- 1 | # 深入浅出STL(一) 2 | 3 | 在这个项目中,我主要参考了侯捷老师的《STL 源码剖析》,并借鉴了书中提及的 SGI STL 以及 GitHub 上的 MyTinySTL 项目。目前,我已经完成了 `vector` 的一些基本操作,尽管如此,我已经学到了很多。在从未深入学习到逐渐掌握的过程中,我对后续的工作有了清晰的思路,因此决定在此告一段落。 4 | 5 | 在完成这个项目的过程中,我对 STL 的工作方式有了更深刻的理解。我学会了内存池的概念,甚至考虑模仿一些流行的内存池代码再做一个。此外,我对模板元编程也有了更深入的认识,解决了之前一些模棱两可的模板写法问题。 6 | 7 | ## 1. STL 是什么? 8 | 9 | 在认真了解之前,我对 STL 的理解仅限于一些常见的容器如 `vector`、`map`、`set` 等。然而,STL 远不止这些。STL(Standard Template Library)是一套功能强大的程序库,提供了以下六个主要组件: 10 | 11 | - **容器(Containers)**:用于存储和组织数据,如 `vector`、`list`、`deque` 等。 12 | - **算法(Algorithms)**:处理数据的通用算法,如排序、搜索、变换等。 13 | - **迭代器(Iterators)**:连接容器与算法的桥梁,提供了统一的访问方式。 14 | - **仿函数(Functors)**:类似于函数的对象,可以像函数一样被调用。 15 | - **适配器(Adapters)**:将一种接口转换为另一种接口,使不同组件之间能够协同工作。 16 | - **分配器(Allocators)**:负责内存的分配和管理。 17 | 18 | 其中,容器、算法和迭代器是我相对熟悉的部分,而分配器、仿函数和适配器则相对陌生。了解这些组件的存在及其重要性是进一步理解 STL 的关键。尤其是分配器的存在意义是什么?接下来,我们将从分配器(Allocator)开始,逐步揭开 STL 的神秘面纱。 19 | 20 | ## 2. 构造函数中隐藏的默认参数:分配器 21 | 22 | 在 STL 中,几乎所有的容器类(如 `vector`、`list`、`map` 等)都在构造函数中接受一个名为“分配器(Allocator)”的参数。这个分配器负责管理内存的分配和释放,而标准库提供的默认分配器 `std::allocator` 通常已经足够使用。因此,容器的构造函数通常隐藏了这个参数,使得用户不必明确传递它。以 SGI STL 的代码为例: 23 | 24 | ```cpp 25 | template 26 | class vector : protected _Vector_base<_Tp, _Alloc> { 27 | //... 28 | } 29 | ``` 30 | 31 | 第二个默认的模板参数 `__STL_DEFAULT_ALLOCATOR(_Tp)` 就是 SGI STL 默认的空间配置器。既然 STL 容器默认使用分配器,那么自然会引发一个问题:为什么需要专门的分配器,而不是直接使用 C++ 中常见的 `new` 操作符呢?要回答这个问题,我们首先需要深入理解 `new` 的工作原理及其局限性。 32 | 33 | ## 3. `new` 操作符的工作原理 34 | 35 | 在 C++ 中,`new` 实际上有三种主要形式:`new` 操作符、`operator new` 函数和 `placement new`。理解这三种形式有助于我们更好地理解为什么 STL 需要分配器。 36 | 37 | ### 3.1 `new` 操作符 38 | 39 | 这是最常用的形式: 40 | 41 | ```cpp 42 | int* p = new int(10); 43 | ``` 44 | 45 | `new` 操作符完成了两件事:1. 分配内存;2. 在分配的内存上构造对象。 46 | 47 | ### 3.2 `operator new` 函数 48 | 49 | 这是一个可以被重载的函数: 50 | 51 | ```cpp 52 | void* operator new(size_t size); 53 | ``` 54 | 55 | `operator new` 只负责分配内存,不调用构造函数。它通常被 `new` 操作符在内部调用。 56 | 57 | ### 3.3 `placement new` 58 | 59 | 这是 `new` 操作符的一个特殊版本,允许我们在预先分配的内存上构造对象: 60 | 61 | ```cpp 62 | char* buffer = new char[sizeof(MyClass)]; 63 | MyClass* obj = new (buffer) MyClass(); 64 | ``` 65 | 66 | `placement new` 不分配新内存,只在指定的内存位置上调用构造函数。它主要用于精细控制对象的生命周期和内存使用,例如在嵌入式系统或性能要求极高的应用中。 67 | 68 | ## 4. 为什么需要分配器? 69 | 70 | 尽管 `new` 操作符可以完成内存分配和对象构造的功能,但它还是存在一些局限性: 71 | 72 | - **效率问题**:`new` 操作符每次都需要向操作系统申请内存,这个过程开销较大。而分配器可以采用内存池技术,预先分配一块大内存,然后在这块内存上进行快速分配。 73 | - **灵活性问题**:`new` 操作符无法满足不同容器对内存分配的个性化需求,如对齐要求、内存碎片管理等。分配器可以根据具体需求实现定制化的内存管理策略。 74 | - **复杂性问题**:将内存管理的细节封装在分配器中,可以简化容器的实现,使得容器的代码更加简洁和通用。 75 | 76 | 在 STL 中,分配器的主要职责是管理内存的分配和释放。标准库的默认分配器 `std::allocator` 提供了一种通用的内存管理方式,它使用了底层的 `new` 和 `delete` 操作符。然而,开发者可以根据特定需求`自定义`分配器,从而在特定场景下实现更复杂的内存管理策略,如内存池。通过自定义分配器,可以为不同的容器定制专门的内存管理机制,以此提高内存分配的效率和整体性能。 77 | 78 | ### 4.1 内存池概念 79 | 80 | 内存池(Memory Pool)是一种内存分配策略,旨在提高动态内存分配的效率。它的基本思想是预先分配一大块内存,然后将这块内存分成多个小块进行管理。当程序需要分配内存时,直接从预分配的内存池中获取,而不是每次都向操作系统申请。这样做有以下几个优点: 81 | 82 | 1. **减少系统调用**:预分配大块内存减少了向操作系统申请内存的次数,降低了系统调用的开销。 83 | 2. **提高分配速度**:从预分配的内存中分配小块内存通常比直接调用 `new` 或 `malloc` 更快。 84 | 3. **减少内存碎片**:通过统一管理内存,可以更好地控制内存布局,减少内存碎片的产生。 85 | 4. **更好的缓存局部性**:预分配的内存往往在物理内存上是连续的,有利于提高缓存命中率。 86 | 5. **自定义内存管理策略**:可以根据具体应用场景优化内存分配策略,如为特定大小的对象预留内存块。 87 | 88 | ### 4.2 高频交易系统中的订单管理 89 | 90 | 想象一个场景:我们正在开发一个高性能的股票交易系统,需要频繁地创建和销毁大量的小对象(比如交易订单)。在这种情况下,STL 分配器的优势就变得非常明显。 91 | 92 | 假设我们有一个 `TradeOrder` 类: 93 | 94 | ```cpp 95 | class TradeOrder { 96 | std::string stockSymbol; 97 | double price; 98 | int quantity; 99 | // 其他成员... 100 | }; 101 | ``` 102 | 103 | 在传统方法中,我们可能会这样管理订单: 104 | 105 | ```cpp 106 | std::vector orders; 107 | 108 | // 创建新订单 109 | orders.push_back(new TradeOrder()); 110 | 111 | // 处理完订单后 112 | for (auto order : orders) { 113 | delete order; 114 | } 115 | orders.clear(); 116 | ``` 117 | 118 | 这种方法在高频交易环境下可能会遇到以下问题: 119 | 120 | 1. 频繁的内存分配和释放会导致内存碎片。 121 | 2. 系统调用(如 `malloc`/`free`)的开销会累积,影响性能。 122 | 123 | 现在,来看一下使用 STL 分配器的方案: 124 | 125 | ```cpp 126 | #include 127 | #include 128 | 129 | // 自定义分配器,使用内存池技术 130 | template 131 | class PoolAllocator { 132 | // 实现内存池逻辑... 133 | public: 134 | T* allocate(std::size_t n) { 135 | // 从内存池中分配内存 136 | } 137 | void deallocate(T* p, std::size_t n) { 138 | // 将内存返回到内存池 139 | } 140 | // 其他必要的方法... 141 | }; 142 | 143 | // 使用自定义分配器的 vector 144 | std::vector> orders; 145 | 146 | // 创建新订单 147 | orders.emplace_back(); // 直接在 vector 中构造对象 148 | 149 | // 处理完订单后 150 | orders.clear(); // 自动调用析构函数并将内存返回到内存池 151 | ``` 152 | 153 | 这种方法的优势: 154 | 155 | 1. **内存重用**:`PoolAllocator` 可以维护一个内存池,减少系统调用和内存碎片。 156 | 2. **批量分配**:可以预先分配大块内存,减少分配次数。 157 | 3. **缓存局部性**:相关的对象更可能被分配在相邻的内存位置,提高缓存命中率。 158 | 4. **自定义策略**:可以根据特定需求(如对象大小、生命周期)优化分配策略。 159 | 5. **异常安全**:STL 容器会正确处理异常,确保资源不会泄露。 160 | 161 | 在这个高频交易的例子中,使用自定义的 STL 分配器可以显著提高系统的性能和稳定性。通过减少内存碎片、降低系统调用开销,并提高内存访问的局部性,我们可以处理更多的交易订单,同时保持较低的延迟。 162 | 163 | ### 4.3 多线程环境中的分配器 164 | 165 | 在多线程环境下使用分配器时,需要特别注意线程安全问题。标准的 `std::allocator` 在多线程下是安全的,但自定义分配器在设计时需要考虑同步机制,以确保在多线程并发访问时不会引发竞态条件或数据损坏。 166 | -------------------------------------------------------------------------------- /tutorial2.md: -------------------------------------------------------------------------------- 1 | # 深入浅出STL(二):动手实现一个简单的分配器 2 | 3 | ## 1. 为什么要自定义分配器? 4 | 5 | 自定义分配器的主要目的是在特定场景下提高程序的性能,特别是在频繁的内存分配和释放操作中。通过更高效的内存管理,我们可以减少系统调用、降低内存碎片,从而提升程序的整体效率。在一些高性能应用场景中,标准的 `new` 操作符可能无法满足需求,这时就需要我们动手实现更为高效的分配器。 6 | 7 | ## 2. 实现一个简单的 `MallocAllocator` 8 | 9 | ### 2.1 `MallocAllocator` 的基本职责 10 | 11 | `MallocAllocator` 是一个基础的分配器,它直接使用 C 库中的 `malloc` 和 `free` 来管理内存。尽管实现简单,但它为理解更复杂的分配器(如内存池)奠定了基础。下面是 `MallocAllocator` 的实现代码: 12 | 13 | ```cpp 14 | class MallocAllocator { 15 | public: 16 | static void* Allocate(size_t size) { 17 | void *result = malloc(size); 18 | if (result == nullptr) { result = MallocInOom(size); } 19 | return result; 20 | } 21 | 22 | static void Deallocate(void *obj, size_t /*size*/) { 23 | free(obj); 24 | } 25 | 26 | // 设置自定义的内存不足处理函数 27 | static void (*SetMallocHandler(void (*func)()))() { 28 | void (*old)() = CustomerOomHandler; 29 | CustomerOomHandler = func; 30 | return old; 31 | } 32 | 33 | private: 34 | // 内存不足时的处理函数 35 | static void* MallocInOom(size_t size); 36 | static void (*CustomerOomHandler)(); 37 | }; 38 | ``` 39 | 40 | ### 2.2 处理内存不足的情况 41 | 42 | 当 `malloc` 无法分配内存时,`MallocAllocator` 会调用用户自定义的内存不足处理函数 `CustomerOomHandler`,并尝试再次分配内存。下面是 `MallocInOom` 函数的实现: 43 | 44 | ```cpp 45 | void* MallocAllocator::MallocInOom(size_t size) { 46 | void (*my_malloc_handler)(); 47 | void *result; 48 | 49 | while (true) { 50 | my_malloc_handler = CustomerOomHandler; 51 | 52 | if (my_malloc_handler == nullptr) { 53 | std::abort(); // 若未设置处理函数,直接终止程序 54 | } 55 | 56 | (*my_malloc_handler)(); // 调用用户定义的处理函数 57 | result = malloc(size); // 再次尝试分配内存 58 | 59 | if (result) { return result; } 60 | } 61 | } 62 | ``` 63 | 64 | ## 3. 引入内存池技术 65 | 66 | ### 3.1 内存池的设计思路 67 | 68 | 内存池通过预先分配一大块内存来减少系统调用,并在这块内存中快速分配小块内存,从而提升分配速度并减少内存碎片。内存池特别适合小对象的频繁分配和释放,因为它避免了频繁的系统调用,提高了效率。 69 | 70 | ### 3.2 实现 `MemoryPoolAllocator` 71 | 72 | `MemoryPoolAllocator` 基于 `MallocAllocator` 实现,使用内存池技术管理小内存块的分配和释放。对于较大的内存分配需求,仍然使用 `MallocAllocator`。以下是 `MemoryPoolAllocator` 的实现: 73 | 74 | ```cpp 75 | class MemoryPoolAllocator { 76 | public: 77 | static void* Allocate(size_t size) { 78 | if (size > kMaxBytes) { 79 | return MallocAllocator::Allocate(size); 80 | } 81 | size_t index = GetFreelistIndex(size); 82 | if (freelist_[index].Empty()) { 83 | return ReFill(RoundUp(size)); 84 | } else { 85 | return freelist_[index].Pop(); 86 | } 87 | } 88 | 89 | static void Deallocate(void *obj, size_t size) { 90 | if (size > kMaxBytes) { 91 | MallocAllocator::Deallocate(obj, size); 92 | return; 93 | } 94 | 95 | freelist_[GetFreelistIndex(size)].Push(obj); 96 | } 97 | 98 | private: 99 | static size_t RoundUp(size_t bytes) { return ((bytes + kAlign - 1) & ~(kAlign - 1)); } 100 | static size_t GetFreelistIndex(size_t bytes) { return ((bytes + kAlign - 1) / kAlign - 1); } 101 | static void* ReFill(size_t size); 102 | static char* ChunkAlloc(size_t size, size_t &chunknums); 103 | 104 | static MemoryPoolList freelist_[kFreeListNum]; 105 | static char *freespacestart_; 106 | static char *freespaceend_; 107 | static size_t mallocoffset_; 108 | }; 109 | ``` 110 | 111 | ### 3.3 内存池核心函数的实现 112 | 113 | `ChunkAlloc` 和 `ReFill` 是内存池分配器的核心函数。`ChunkAlloc` 用于从系统中获取新的大块内存,并将其分割成小块供分配使用,而 `ReFill` 用于当 `freelist` 中的空闲链表为空时,填充新的内存块。 114 | 115 | ```cpp 116 | char* MemoryPoolAllocator::ChunkAlloc(size_t size, size_t &chunknums) { 117 | char *result; 118 | size_t bytesneed = size * chunknums; 119 | size_t bytesleft = freespaceend_ - freespacestart_; 120 | 121 | if (bytesleft >= bytesneed) { 122 | result = freespacestart_; 123 | freespacestart_ += bytesneed; 124 | return result; 125 | } else if (bytesleft >= size) { 126 | chunknums = bytesleft / size; 127 | bytesneed = chunknums * size; 128 | result = freespacestart_; 129 | freespacestart_ += bytesneed; 130 | return result; 131 | } else { 132 | size_t bytesget = 2 * bytesneed + RoundUp(mallocoffset_ >> 4); 133 | 134 | if (bytesleft >= kAlign) { 135 | freelist_[GetFreelistIndex(bytesleft)].Push(freespacestart_); 136 | } 137 | 138 | freespacestart_ = (char *)malloc(bytesget); 139 | 140 | if (freespacestart_ == nullptr) { 141 | for (size_t i = size; i <= kMaxBytes; i += kAlign) { 142 | if (!freelist_[GetFreelistIndex(i)].Empty()) { 143 | freespacestart_ = (char*)freelist_[GetFreelistIndex(i)].Pop(); 144 | freespaceend_ = freespacestart_ + i; 145 | return ChunkAlloc(size, chunknums); 146 | } 147 | } 148 | 149 | freespaceend_ = nullptr; 150 | freespacestart_ = (char *)MallocAllocator::Allocate(bytesget); 151 | } 152 | 153 | mallocoffset_ += bytesget; 154 | freespaceend_ = freespacestart_ + bytesget; 155 | return ChunkAlloc(size, chunknums); 156 | } 157 | } 158 | 159 | void* MemoryPoolAllocator::ReFill(size_t size) { 160 | size_t chunknums = 20; 161 | char *chunk = ChunkAlloc(size, chunknums); 162 | char *nextchunk = chunk + size; 163 | 164 | if (chunknums == 1) { 165 | return chunk; 166 | } 167 | 168 | for (int i = 1; i < chunknums; ++i) { 169 | freelist_[GetFreelistIndex(size)].Push(nextchunk); 170 | nextchunk += size; 171 | } 172 | 173 | return chunk; 174 | } 175 | ``` 176 | 177 | ## 4. 合并分配器接口 178 | 179 | 为了方便使用,我们可以将 `MallocAllocator` 和 `MemoryPoolAllocator` 合并成一个统一的接口,并根据分配的内存块大小选择最合适的分配器进行操作。 180 | 181 | ### 4.1 统一接口的实现 182 | 183 | 我们可以通过定义一个 `AllocatorWrapper` 模板类来实现这一点,该类可以根据宏定义灵活地选择使用哪种分配器: 184 | 185 | ```cpp 186 | #define USEMALLOC 187 | #ifdef USEMALLOC 188 | using Allo = MemoryPoolAllocator; 189 | #else 190 | using Allo = MallocAllocator; 191 | #endif 192 | 193 | template 194 | class AllocatorWrapper { 195 | public: 196 | static T* Allocate(size_t n) { return 0 == n ? 0 : (T*)Allocator::Allocate(n * sizeof(T)); } 197 | static T* Allocate(void) { return (T*)Allocator::Allocate(sizeof(T)); } 198 | static void Deallocate(T* p, size_t n) { if (0 != n) { Allocator::Deallocate(p, n * sizeof(T)); } } 199 | static void Deallocate(T* p) { Allocator::Deallocate(p, sizeof(T)); } 200 | }; 201 | ``` 202 | 203 | 通过这种设计,我们不仅简化了分配器的使用,还能灵活地在不同的内存管理策略之间切换,以获得最佳性能。`AllocatorWrapper` 可以作为 STL 容器的默认分配器使用,充分利用自定义分配器的优势。 204 | 205 | ## 5. 分离内存管理与对象管理 206 | 207 | 在分配器的设计中,我们不仅要关注内存的分配和释放,还要关注对象的构造和析构。空间的配置和对象的构造其实是两个独立的过程:前者只负责内存的分配,而后者则负责对象的初始化。同样的,在释放内存之前,我们还需要先析构对象。 208 | 209 | 为了处理这一点,我们引入了 `Construct` 和 `Destroy` 函数,用于对象的构造和析构。 210 | 211 | ### 5.1 构造和析构的实现 212 | 213 | 下面是 `easystl` 命名空间下的一些基本构造和析构函数的实现: 214 | 215 | ```cpp 216 | #ifndef EASYSTL_CONSTRUCTOR_H_ 217 | #define EASYSTL_CONSTRUCTOR_H_ 218 | 219 | #include 220 | 221 | namespace easystl { 222 | 223 | // 默认构造函数 224 | template 225 | inline void Construct(T* p) { 226 | new(p) T(); 227 | } 228 | 229 | // 带值构造函数 230 | template 231 | inline void Construct(T1* p, const T2 & value) { 232 | new(p) T1(value); 233 | } 234 | 235 | // 析构函数 236 | template 237 | inline void Destroy(T* p) { 238 | p->~T(); 239 | } 240 | 241 | // 对区间内的对象进行析构 242 | template 243 | inline void Destroy(ForwardIterator first, ForwardIterator last) { 244 | for(; first != last; ++first) { 245 | Destroy(&*first); 246 | } 247 | } 248 | 249 | } // namespace easystl 250 | 251 | #endif // EASYSTL_CONSTRUCTOR_H_ 252 | ``` 253 | 254 | ### 5.2 `Construct` 与 `Destroy` 的作用 255 | 256 | - **`Construct` 函数**:该函数用于在已经分配好的内存空间上构造对象。这里使用了 **placement new** 语法,即在指定的内存地址上直接调用对象的构造函数。这允许我们在使用分配器分配的内存上手动构造对象。 257 | 258 | - **`Destroy` 函数**:用于手动调用对象的析构函数。C++ 中的析构函数负责释放对象占用的资源,调用 `Destroy` 函数可以确保在释放内存之前正确地销毁对象。 259 | 260 | 通过 `Construct` 和 `Destroy` 函数,我们将内存的分配/释放与对象的构造/析构明确分开,这样的设计符合 C++ 标准库分配器的设计思想,也提高了代码的灵活性和可维护性。 261 | 262 | ### 5.3 结合分配器与构造器 263 | 264 | 在实际使用分配器时,我们可以通过以下步骤来分配和管理对象: 265 | 266 | 1. **分配内存**:首先使用分配器分配足够的内存空间,用于存放对象。 267 | 2. **构造对象**:在分配好的内存空间上调用 `Construct` 函数,构造对象。 268 | 3. **使用对象**:在构造完成后,对象可以正常使用。 269 | 4. **析构对象**:当对象不再需要时,调用 `Destroy` 函数析构对象。 270 | 5. **释放内存**:最后,使用分配器释放分配的内存。 271 | 272 | 这种分离的设计使得内存管理更加灵活,也更加符合 C++ 的 RAII(Resource Acquisition Is Initialization)原则。 273 | 274 | ## 结语 275 | 276 | 我们实现了一个基础的 `MallocAllocator`,并在此基础上引入了更复杂的 `MemoryPoolAllocator`。内存池技术的引入显著提高了内存分配的效率,特别是在处理大量小对象时。然后,我们通过一个统一的接口合并了两种分配器,使得在不同场景下能够灵活选择合适的分配器。最后,通过引入 `Construct` 和 `Destroy` 函数,我们进一步完善了分配器的功能,将内存的配置与对象的构造分开处理。这种设计不仅提高了内存管理的灵活性,还能更好地满足不同场景下的需求。 277 | 278 | 279 | -------------------------------------------------------------------------------- /tutorial3.md: -------------------------------------------------------------------------------- 1 | # 深入浅出STL(三):迭代器概述与设计探讨 2 | 3 | 在本教程中,我们将深入探讨迭代器的基本概念、它在C++标准模板库(STL)中的重要性,以及迭代器的设计细节。通过这一教程,你将更好地理解迭代器与容器的关系,并掌握迭代器设计的关键要素。 4 | 5 | ## 1. 迭代器:泛化的指针 6 | 7 | 在介绍迭代器之前,我们先来谈谈指针。迭代器的设计灵感来源于指针,甚至可以将迭代器看作一种**泛化的指针**,而指针则是最简单的一种迭代器。 8 | 9 | ```cpp 10 | #include 11 | 12 | int main() { 13 | int arr[] = {1, 2, 3, 4, 5}; 14 | int* p = arr; 15 | while (p != arr + 5) { // 指针作为迭代器遍历数组 16 | std::cout << *p << ' '; 17 | ++p; 18 | } 19 | std::cout << std::endl; 20 | return 0; 21 | } 22 | ``` 23 | 24 | 在上面的代码中,`p` 是一个指针,它通过自增操作逐一访问数组中的元素。指针的这种行为正是迭代器概念的核心:**对容器内元素进行访问和遍历**。 25 | 26 | ## 2. 为什么需要迭代器? 27 | 28 | 迭代器为容器提供了一种标准化的接口,使得我们能够以统一的方式对不同类型的容器进行操作。无论是数组、链表,还是更复杂的容器类型,如 `std::vector` 或 `std::map`,都有对应的迭代器。迭代器的存在,使得容器和算法可以解耦合,从而提高代码的灵活性和通用性。 29 | 30 | ### 2.1 如果没有迭代器 31 | 32 | 假设没有迭代器,我们对容器的访问和遍历将会如何?让我们来看几个例子: 33 | 34 | #### 2.1.1 访问和遍历 35 | 36 | 1. **数组**:通过索引访问。 37 | ```cpp 38 | int arr[] = {1, 2, 3, 4, 5}; 39 | for (int i = 0; i < 5; ++i) { 40 | std::cout << arr[i] << " "; 41 | } 42 | ``` 43 | 44 | 2. **向量**:通过索引访问。 45 | ```cpp 46 | std::vector vec = {1, 2, 3, 4, 5}; 47 | for (size_t i = 0; i < vec.size(); ++i) { 48 | std::cout << vec[i] << " "; 49 | } 50 | ``` 51 | 52 | 3. **列表**:通过范围 `for` 循环。 53 | ```cpp 54 | std::list lst = {1, 2, 3, 4, 5}; 55 | for (int num : lst) { 56 | std::cout << num << " "; 57 | } 58 | ``` 59 | 60 | 4. **映射**:通过键值对遍历。 61 | ```cpp 62 | std::map map = {{1, "one"}, {2, "two"}, {3, "three"}}; 63 | for (const auto& kv : map) { 64 | std::cout << kv.first << ": " << kv.second << std::endl; 65 | } 66 | ``` 67 | 68 | 5. **集合**:通过范围 `for` 循环。 69 | ```cpp 70 | std::set set = {1, 2, 3, 4, 5}; 71 | for (int num : set) { 72 | std::cout << num << " "; 73 | } 74 | ``` 75 | 76 | #### 2.1.2 算法的实现 77 | 78 | 假设我们需要设计一个打印的接口: 79 | ```cpp 80 | void printVector(const std::vector& vec) { 81 | for (size_t i = 0; i < vec.size(); ++i) { 82 | std::cout << vec[i] << " "; 83 | } 84 | std::cout << std::endl; 85 | } 86 | 87 | void printList(const std::list& lst) { 88 | for (const int& num : lst) { 89 | std::cout << num << " "; 90 | } 91 | std::cout << std::endl; 92 | } 93 | 94 | int main() { 95 | std::vector vec = {1, 2, 3, 4, 5}; 96 | std::list lst = {6, 7, 8, 9, 10}; 97 | 98 | printVector(vec); 99 | printList(lst); 100 | 101 | return 0; 102 | } 103 | ``` 104 | 105 | 没有迭代器时,每个容器需单独的访问和遍历逻辑,代码冗长且难以维护。 106 | 107 | ### 2.2 迭代器的优势 108 | 109 | 迭代器为我们带来了极大的便利,简化了对各种容器的操作。我们可以通过统一的接口来遍历不同类型的容器,从而避免冗余代码,提高代码的可维护性。例如,上述的打印接口可以这样实现: 110 | 111 | ```cpp 112 | template 113 | void print(const Container& container) { 114 | for (auto it = container.begin(); it != container.end(); ++it) { 115 | std::cout << *it << " "; 116 | } 117 | std::cout << std::endl; 118 | } 119 | 120 | int main() { 121 | std::vector vec = {1, 2, 3, 4, 5}; 122 | std::list lst = {6, 7, 8, 9, 10}; 123 | std::set set = {11, 12, 13, 14, 15}; 124 | 125 | print(vec); 126 | print(lst); 127 | print(set); 128 | 129 | return 0; 130 | } 131 | ``` 132 | 133 | 通过使用迭代器,我们能够统一对不同容器的操作,极大地减少了代码的重复性。无论是 `vector`、`list` 还是 `set`,都可以使用同一个 `print` 函数来遍历和打印内容,从而提高代码的复用性和灵活性。 134 | 135 | ## 3. 迭代器的设计 136 | 137 | 在C++标准模板库(STL)中,迭代器是一个核心的概念。它们本质上是为容器提供的一种“桥梁”,让算法和容器之间能够通过统一的方式进行交互。那么迭代器是怎么做到这个效果的呢? 138 | 139 | ### 3.1 重载的操作符 140 | 141 | STL迭代器的设计非常巧妙,它们通过重载一系列操作符,使得迭代器可以像指针一样使用。常见的重载操作符包括: 142 | 143 | - `*`:解引用操作符,用于获取迭代器当前指向的元素。 144 | - `++`:自增操作符,用于将迭代器移动到下一个元素。 145 | - `--`:自减操作符(仅双向迭代器及以上支持),用于将迭代器移动到前一个元素。 146 | - `==` 和 `!=`:比较操作符,用于判断两个迭代器是否相等。 147 | 148 | 这些操作符的重载,使得不同类型的容器都可以通过相同的接口进行遍历和访问,极大地提高了STL的通用性和灵活性。 149 | 150 | ### 3.2 类型别名定义 151 | 152 | 除了重载操作符,迭代器还需要定义一些类型别名,以便与STL算法和容器进行更好的集成。常见的类型别名包括: 153 | 154 | - `value_type`:迭代器指向的元素类型。 155 | - `difference_type`:两个迭代器之间的距离类型。 156 | - `pointer`:指向元素的指针类型。 157 | - `reference`:元素的引用类型。 158 | - `iterator_category`:迭代器的类别标签。 159 | 160 | 为啥要有这些类型别名呢?我们带着这个问题来看下一小节。 161 | 162 | ### 3.3 `traits` 的引入 163 | 164 | 为了使算法能够适用于不同类型的迭代器,STL引入了 `traits` 机制。`traits` 是一个模板类,通过类型萃取(type traits)访问迭代器的类型信息。这样,算法可以不依赖具体的迭代器类型,而是通过 `traits` 来获取所需的类型信息。 165 | 166 | 以下是针对`iterator`的`iterator_traits` 的一个简单实现: 167 | 168 | ```cpp 169 | template 170 | struct iterator_traits { 171 | using value_type = typename Iterator::value_type; 172 | using difference_type = typename Iterator::difference_type; 173 | using pointer = typename Iterator::pointer; 174 | using reference = typename Iterator::reference; 175 | using iterator_category = typename Iterator::iterator_category; 176 | }; 177 | ``` 178 | 179 | 通过 `iterator_traits`,我们可以在算法中方便地获取迭代器的类型信息。例如: 180 | 181 | ```cpp 182 | template 183 | void my_algorithm(Iterator first, Iterator last) { 184 | using value_type = typename std::iterator_traits::value_type; 185 | // 其他算法实现 186 | } 187 | ``` 188 | 189 | 此时我们能理解到,当需要从迭代器对象推导出容器内部的元素类型时,迭代器内部声明的类型别名就能通过 `iterator_traits` 统一萃取出容器内部元素的类型。这就是上一小节的问题的答案。那么新的问题也来了,不使用 `iterator_traits` 也能得到类似的效果,`iterator_traits` 的意义何在呢? 190 | 191 | ### 3.4 为什么需要 `iterator_traits` 192 | 193 | 考虑这样一个场景,假设我们有一个接受迭代器的算法,我们可以直接使用迭代器内部的类型别名: 194 | 195 | ```cpp 196 | template 197 | void my_algorithm(Iterator first, Iterator last) { 198 | using value_type = typename Iterator::value_type; 199 | // 其他算法实现 200 | } 201 | ``` 202 | 203 | 但是如果参数是指针呢?指针是最简单的迭代器,但指针并没有定义 `value_type`。为了解决这个问题,我们只需在 `iterator_traits` 的定义中针对指针进行偏特化: 204 | 205 | ```cpp 206 | // 针对原生指针类型的特化 207 | template 208 | struct iterator_traits { 209 | using value_type = T; 210 | using difference_type = std::ptrdiff_t; 211 | using pointer = T*; 212 | using reference = T&; 213 | using iterator_category = std::random_access_iterator_tag; 214 | }; 215 | ``` 216 | 217 | 此外,当我们设计一个不在标准库范围内的容器及其专属迭代器时,只需针对该迭代器类型进行 `iterator_traits` 的偏特化,就可以让该容器使用标准库中的算法。 218 | 219 | ### 3.5 迭代器标签及其作用 220 | 221 | C++标准库中的迭代器被划分为几种不同的类别,每个类别都有自己独特的功能特性。这些类别是通过“迭代器标签”来标识的: 222 | 223 | 1. **输入迭代器(Input Iterator)**:支持只读访问,并且只能向前遍历一次。常用于读取输入数据,如流的遍历。 224 | 225 | 2. **输出迭代器(Output Iterator)**:支持只写访问,同样只能向前遍历一次。常用于输出数据,如写入流。 226 | 227 | 3. **前向迭代器(Forward Iterator)**:支持读写访问,能够多次遍历相同的数据。这种迭代器的一个典型应用是单链表。 228 | 229 | 4. **双向迭代器(Bidirectional Iterator)**:在前向迭代器的基础上,增加了反向遍历的能力。双向链表等容器通常会使用这种迭代器。 230 | 231 | 5. **随机访问迭代器(Random Access Iterator)**:支持常数时间的随机访问,允许直接跳转到容器中的任意元素。`std::vector` 和数组的迭代器都是这种类型。 232 | 233 | 通过这些标签,STL中的算法可以根据迭代器类型的不同,自动适应不同的操作需求,从而确保算法的通用性和高效性。 234 | 235 | 236 | ## 结语 237 | 238 | 迭代器作为C++标准模板库中的核心概念之一,通过为容器提供统一的访问接口,极大地简化了算法设计并提高了代码的复用性。理解迭代器的设计原理以及如何实现自定义迭代器,不仅有助于更好地利用STL,还能帮助你在实际编程中创建更加灵活和通用的代码结构。 239 | -------------------------------------------------------------------------------- /tutorial4.md: -------------------------------------------------------------------------------- 1 | # 深入浅出STL(四):构建自己的 `vector` 2 | 3 | 在现代C++编程中,`vector` 是最常用的容器之一。它不仅提供了动态数组的功能,还能自动管理内存,并提供高效的访问和修改操作。然而,对于很多开发者来说,`vector` 的内部实现仍然是个谜。通过自己动手实现一个 `vector`,我们可以深入理解其设计思想,进而提高编程能力。在本教程中,我们将带你一步步实现一个自定义的 `vector`,并深入探讨其设计和实现细节。通过这个过程,你将更好地理解 `vector` 的内部机制,并掌握实现动态数组的关键技术。 4 | 5 | ## 1. 什么是 `vector`? 6 | 7 | 在C++中,`vector` 是一种动态数组,它的大小可以在运行时动态调整。与传统的静态数组相比,`vector` 具有以下几个显著优势: 8 | 9 | - **连续内存存储**:`vector` 内部使用一块连续的内存来存储数据,这使得元素的访问非常高效,特别是当你需要随机访问某个元素时,通过索引可以直接定位到该元素。 10 | 11 | - **动态扩展**:当 `vector` 的容量不足时,它会自动扩展以容纳更多的元素。这种扩展通常是成倍扩展的,以减少频繁扩容的开销。 12 | 13 | - **高效访问**:由于 `vector` 使用连续的内存存储数据,它能够像数组一样通过 `operator[]` 或 `at` 函数高效地访问任意元素。同时,`vector` 也提供了边界检查功能,确保访问安全。 14 | 15 | - **自动内存管理**:`vector` 自动处理内存的分配与释放,避免了手动管理内存可能带来的错误。你不需要担心内存泄漏或未初始化的内存问题,`vector` 会在合适的时机分配和释放内存。 16 | 17 | 此外,`vector` 还支持复杂的数据结构操作,如插入、删除、排序等,并在内部实现了对这些操作的高效支持。 18 | 19 | ## 2. `vector` 的基本操作 20 | 21 | 在构建自己的 `vector` 之前,理解其基本操作是必要的。下面我们将详细介绍 `vector` 的核心操作。 22 | 23 | ### 2.1 添加元素 24 | 25 | - **push_back**:向 `vector` 的末尾添加一个元素。这是 `vector` 中最常用的操作之一。当 `vector` 的容量不足以容纳新元素时,它会自动分配更多的内存来扩展。 26 | 27 | ```cpp 28 | std::vector vec; 29 | vec.push_back(10); 30 | vec.push_back(20); 31 | ``` 32 | 33 | - **insert**:在 `vector` 的指定位置插入一个或多个元素。与 `push_back` 不同,`insert` 会影响插入点后的所有元素,可能导致这些元素在内存中移动。 34 | 35 | ```cpp 36 | std::vector vec = {1, 2, 3}; 37 | vec.insert(vec.begin() + 1, 10); // 在第二个位置插入10 38 | ``` 39 | 40 | ### 2.2 删除元素 41 | 42 | - **pop_back**:从 `vector` 的末尾删除一个元素。这是 `vector` 中与 `push_back` 对应的操作,用于移除最后一个元素。 43 | 44 | ```cpp 45 | std::vector vec = {1, 2, 3}; 46 | vec.pop_back(); // 删除最后一个元素3 47 | ``` 48 | 49 | - **erase**:删除 `vector` 中指定位置的一个或多个元素。这个操作会影响到删除位置后的所有元素,导致它们在内存中的位置发生变化。 50 | 51 | ```cpp 52 | std::vector vec = {1, 2, 3, 4}; 53 | vec.erase(vec.begin() + 1); // 删除第二个元素2 54 | ``` 55 | 56 | ### 2.3 访问元素 57 | 58 | - **operator[]**:通过索引直接访问元素。这个操作符不进行边界检查,因此使用时需要确保索引在有效范围内。 59 | 60 | ```cpp 61 | std::vector vec = {1, 2, 3}; 62 | int value = vec[1]; // 访问第二个元素 63 | ``` 64 | 65 | - **at**:通过索引访问元素,并进行边界检查。如果索引超出范围,会抛出 `std::out_of_range` 异常。 66 | 67 | ```cpp 68 | try { 69 | std::vector vec = {1, 2, 3}; 70 | int value = vec.at(10); // 超出范围,抛出异常 71 | } catch (const std::out_of_range& e) { 72 | std::cerr << "Out of range: " << e.what() << std::endl; 73 | } 74 | ``` 75 | 76 | ## 3. `vector` 的内存管理 77 | 78 | `vector` 作为动态数组,内存管理是其核心功能之一。我们需要了解 `vector` 是如何管理其内部内存的。 79 | 80 | ### 3.1 动态内存分配 81 | 82 | 当 `vector` 中的元素超出当前容量时,`vector` 需要分配新的内存,并将现有元素复制到新的内存空间中。这一过程叫做“扩容”。通常情况下,`vector` 会成倍扩展其容量,以减少频繁扩容的成本。由于 `vector` 使用的是连续内存,因此扩容时需要将所有元素从旧的内存块复制到新的、更大的内存块中。 83 | 84 | ### 3.2 容量与大小管理 85 | 86 | - **size**:`vector` 当前保存的元素个数。可以通过 `size()` 函数获取。 87 | 88 | ```cpp 89 | std::vector vec = {1, 2, 3}; 90 | std::cout << "Size: " << vec.size() << std::endl; // 输出 3 91 | ``` 92 | 93 | - **capacity**:`vector` 在当前内存分配中可以容纳的最大元素个数。通过 `capacity()` 函数可以查看。这通常比 `size` 大,因为 `vector` 会预留一些额外的空间来减少频繁的内存分配。 94 | 95 | ```cpp 96 | std::cout << "Capacity: " << vec.capacity() << std::endl; // 可能输出大于3的值 97 | ``` 98 | 99 | - **reserve**:用于预先分配内存,避免在未来的插入操作中频繁扩容。`reserve()` 函数可以将 `vector` 的容量设置为至少指定的值,从而在插入大量元素前减少扩容的次数。 100 | 101 | ```cpp 102 | vec.reserve(100); // 将容量至少扩展到100 103 | ``` 104 | 105 | - **shrink_to_fit**:减少 `vector` 的容量,使其与当前大小相匹配。调用 `shrink_to_fit()` 之后,`vector` 将尽可能释放未使用的内存。这在减少内存占用时非常有用。 106 | 107 | ```cpp 108 | vec.shrink_to_fit(); // 释放多余的内存 109 | ``` 110 | 111 | ## 4. 实现一个简单的 `vector` 类 112 | 113 | 现在,我们开始动手实现一个自己的 `vector`。这个实现会包含最基础的功能,帮助我们理解 `vector` 的核心机制。 114 | 115 | ### 4.1 类的定义与基本成员 116 | 117 | 首先,我们定义一个类模板,并声明一些必要的成员变量。 118 | 119 | ```cpp 120 | template> // 使用自定义分配器 121 | class vector { 122 | private: 123 | T* begin_; // 指向内存的起始位置 124 | T* end_; // 指向内存的末尾位置 125 | T* capacity_; // 指向内存的容量末尾位置 126 | 127 | public: 128 | // 构造函数、析构函数等成员函数的声明 129 | }; 130 | ``` 131 | 132 | ### 4.2 构造函数与析构函数 133 | 134 | 构造函数用于初始化 `vector`,而析构函数用于清理资源。 135 | 136 | - **默认构造函数**:初始化一个空的 `vector`。 137 | 138 | ```cpp 139 | vector() noexcept : begin_(nullptr), end_(nullptr), capacity_(nullptr) {} 140 | ``` 141 | 142 | - **带大小和初始值的构造函数**:创建一个指定大小的 `vector`,并用初始值填充。 143 | 144 | ```cpp 145 | vector(size_type len, const T& value) noexcept { 146 | NumsInit(len, value); // 初始化函数,后文详述 147 | } 148 | ``` 149 | 150 | - **拷贝构造函数**:实现深拷贝,确保 `vector` 在复制时行为正确。 151 | 152 | ```cpp 153 | vector(const vector& other) noexcept { 154 | RangeInit(other.begin_, other.end_); // 范围初始化函数,后文详述 155 | } 156 | ``` 157 | 158 | - **析构函数**:释放动态分配的内存,避免内存泄漏。 159 | 160 | ```cpp 161 | ~vector() noexcept { 162 | DestroynDeallocate(begin_, end_, static_cast(capacity_ - begin_)); // 资源清理函数,后文详述 163 | } 164 | ``` 165 | 166 | ### 4.3 内存管理函数 167 | 168 | 为了高效管理内存,增加代码可读性以及减少重复代码,我们需要实现 `NumsInit` 、`RangeInit` 和 `DestroynDeallocate` 函数。 169 | 170 | - **NumsInit**:初始化指定数量的元素,并填充初始值。 171 | 172 | ```cpp 173 | void NumsInit(size_type n, const T& value) noexcept { 174 | size_type initsize = easystl::Max(static_cast(n), static_cast(16)); // 起码分配16个 175 | begin_ = DataAllocator::Allocate(initsize); 176 | end_ = easystl::uninitialized_fill_n(begin_, n, value); // 在未初始化的内存上构造对象 177 | capacity_ = begin_ + initsize; 178 | } 179 | ``` 180 | 181 | - **RangeInit**:从一个迭代器范围初始化 `vector`。 182 | 183 | ```cpp 184 | template 185 | void RangeInit(Iter first, Iter last) noexcept { 186 | size_type initsize = easystl::Max(static_cast(last - first), static_cast(16)); 187 | begin_ = DataAllocator::Allocate(initsize); 188 | end_ = easystl::uninitialized_copy(first, last, begin_); // 在未初始化的内存上构造对象 189 | capacity_ = begin_ + initsize; 190 | } 191 | ``` 192 | 193 | - **DestroynDeallocate**:销毁并释放内存。 194 | 195 | ```cpp 196 | void DestroynDeallocate(iterator first, iterator last, size_type len) noexcept { 197 | if (first) { 198 | Destroy(first, last); // 销毁对象 199 | DataAllocator::Deallocate(first, len); // 释放内存 200 | } 201 | } 202 | ``` 203 | 204 | ### 4.4 全局初始化辅助函数 205 | 206 | 为了更好地管理内存,我们需要实现一些全局辅助函数。这些函数用于在未初始化的内存上构造对象。主要包括 `uninitialized_copy`、`uninitialized_fill` 和 `uninitialized_fill_n`。 207 | 208 | - **uninitialized_copy**:从输入迭代器范围 `[first, last)` 复制元素到未初始化的内存区域 `[result, result + (last - first))`。 209 | 210 | ```cpp 211 | template 212 | ForwardIter uninitialized_copy(InputIter first, InputIter last, ForwardIter result) { 213 | auto current = result; 214 | try { 215 | for (; first != last; ++first, ++current) { 216 | easystl::Construct(&*current, *first); // 在当前位置构造元素 217 | } 218 | } 219 | catch (...) { 220 | easystl::Destroy(result, current); // 如果发生异常,销毁已经构造的元素 221 | std::abort(); // 终止程序 222 | } 223 | return current; // 返回最后一个构造元素的下一个位置 224 | } 225 | ``` 226 | 227 | - **参数说明**: 228 | - `first`:输入范围的起始迭代器。 229 | - `last`:输入范围的结束迭代器。 230 | - `result`:目标内存区域的起始迭代器。 231 | 232 | - **工作流程**: 233 | 1. 从 `first` 开始,逐个元素复制到 `result` 指向的未初始化内存区域。 234 | 2. 如果在复制过程中发生异常,销毁已经构造的元素并终止程序。 235 | 236 | - **uninitialized_fill**:在未初始化的内存区域 `[first, last)` 构造并填充指定的值。 237 | 238 | ```cpp 239 | template 240 | void uninitialized_fill(ForwardIter first, ForwardIter last, const T& value) { 241 | auto current = first; 242 | try { 243 | for (; current != last; ++current) { 244 | easystl::Construct(&*current, value); // 在当前位置构造元素 245 | } 246 | } 247 | catch (...) { 248 | easystl::Destroy(first, current); // 如果发生异常,销毁已经构造的元素 249 | std::abort(); // 终止程序 250 | } 251 | } 252 | ``` 253 | 254 | - **参数说明**: 255 | - `first`:目标内存区域的起始迭代器。 256 | - `last`:目标内存区域的结束迭代器。 257 | - `value`:要填充的值。 258 | 259 | - **工作流程**: 260 | 1. 从 `first` 开始,在未初始化的内存区域构造并填充 `value`。 261 | 2. 如果在填充过程中发生异常,销毁已经构造的元素并终止程序。 262 | 263 | - **uninitialized_fill_n**:在未初始化的内存区域 `[first, first + n)` 构造并填充指定数量的值。 264 | 265 | ```cpp 266 | template 267 | ForwardIter uninitialized_fill_n(ForwardIter first, Size n, const T& value) { 268 | auto current = first; 269 | try { 270 | for (; n > 0; --n, ++current) { 271 | easystl::Construct(&*current, value); // 在当前位置构造元素 272 | } 273 | } 274 | catch (...) { 275 | easystl::Destroy(first, current); // 如果发生异常,销毁已经构造的元素 276 | std::abort(); // 终止程序 277 | } 278 | return current; // 返回最后一个构造元素的下一个位置 279 | } 280 | ``` 281 | 282 | - **参数说明**: 283 | - `first`:目标内存区域的起始迭代器。 284 | - `n`:要构造的元素数量。 285 | - `value`:要填充的值。 286 | 287 | - **工作流程**: 288 | 1. 从 `first` 开始,在未初始化的内存区域构造并填充 `n` 个 `value`。 289 | 2. 如果在填充过程中发生异常,销毁已经构造的元素并终止程序。 290 | 291 | 这些全局初始化辅助函数的核心思想是在未初始化的内存区域上构造元素,并且在发生异常时能够安全地清理已经构造的元素,避免内存泄漏。这些函数是实现 `vector` 类内存管理的重要工具。 292 | 293 | ### 4.5 基本操作的实现 294 | 295 | 接下来,我们实现 `vector` 的一些基本操作,如 `push_back`、`pop_back` 以及 `operator[]`。 296 | 297 | - **push_back**:将新元素添加到 `vector` 的末尾。如果容量不足,需要先扩容。 298 | 299 | ```cpp 300 | void push_back(const T& x) noexcept { 301 | if (end_ != capacity_) { 302 | Construct(end_, x); // 在末尾构造新元素 303 | ++end_; 304 | } else { 305 | InsertAux(end_, 1, x); // 如果容量不足,进行扩容后再插入,后文详述 306 | } 307 | } 308 | ``` 309 | 310 | - **pop_back**:移除 `vector` 末尾的元素。 311 | 312 | ```cpp 313 | void pop_back() noexcept { 314 | if (empty()) { return; } 315 | --end_; 316 | Destroy(end_); // 销毁末尾元素 317 | } 318 | ``` 319 | 320 | - **operator[]** 和 **at**:实现元素的直接访问。 321 | 322 | ```cpp 323 | reference operator[] (size_type n) noexcept { return *(begin_ + n); } 324 | reference at(size_type n) noexcept { 325 | if (n >= size()) throw std::out_of_range("Index out of range"); 326 | return *(begin_ + n); 327 | } 328 | ``` 329 | 330 | ### 4.6 `vector`的迭代器 331 | 332 | `vector` 是基于连续内存存储数据的容器,因此它的迭代器可以直接使用**指针**实现。因为迭代器的作用是提供对 `vector` 中元素的直接访问,并支持各种迭代操作。还有跟上一章的`iterator_traits`一样,我们同样需要针对`vector`有一个类似实现。用于我们可以通过`vector`对象获取里面存储的元素的类型、迭代器的类型等内容。 333 | 334 | #### 4.6.1 迭代器类型定义 335 | 336 | 在 `vector` 类中,迭代器类型通常定义为指向元素的指针,并且可以定义 `const_iterator` 来表示只读迭代器。 337 | 338 | ```cpp 339 | template> 340 | class vector { 341 | public: 342 | // 类型别名定义 343 | using value_type = T; 344 | using pointer = T*; 345 | using iterator = T*; 346 | using const_iterator = const T*; 347 | using reference = T&; 348 | using const_reference = const T&; 349 | using size_type = size_t; 350 | using difference_type = ptrdiff_t; 351 | // 其他内容 352 | }; 353 | ``` 354 | 355 | - **`iterator`**:定义为 `T*`,它是一个普通指针,能够访问和修改 `vector` 中的元素。 356 | - **`const_iterator`**:定义为 `const T*`,它是一个只读指针,只能访问元素而不能修改。 357 | 358 | #### 4.6.2 基本迭代器操作 359 | 360 | 通常在 `vector` 中,我们会提供以下几个基本的迭代器操作方法: 361 | 362 | - **`begin()`**:返回指向第一个元素的迭代器。 363 | - **`end()`**:返回指向最后一个元素后面的迭代器(不指向任何有效元素)。 364 | - **`cbegin()`** 和 **`cend()`**:分别返回 `const_iterator` 类型的 `begin` 和 `end`,用于在 `const vector` 对象上进行迭代。 365 | 366 | ```cpp 367 | iterator begin() noexcept { return begin_; } 368 | iterator end() noexcept { return end_; } 369 | const_iterator begin() const noexcept { return begin_; } 370 | const_iterator end() const noexcept { return end_; } 371 | const_iterator cbegin() const noexcept { return begin_; } 372 | const_iterator cend() const noexcept { return end_; } 373 | ``` 374 | 375 | - **`begin()`** 和 **`end()`** 返回普通迭代器,允许修改元素。 376 | - **`cbegin()`** 和 **`cend()`** 返回 `const_iterator`,只允许读取元素。 377 | 378 | ### 4.7 扩容机制: `InsertAux` 的实现 379 | 380 | `vector` 的扩容是其核心功能之一,因为它的大小是动态调整的。当添加新元素时,如果 `vector` 当前的容量不足以容纳新元素,它需要进行扩容。扩容过程涉及到分配新的更大容量的内存,将原有元素复制到新内存中,并释放旧的内存。主要起作用的是一个辅助函数 `InsertAux`,在 `vector` 的末尾插入元素并在必要时进行扩容。其基本工作原理是:如果当前容量足够,则直接插入元素;否则,扩容后再插入元素。 381 | 382 | ```cpp 383 | void InsertAux(iterator pos, size_type nums, const T& x) noexcept { 384 | const size_type newsize = Max(size()*2, static_cast(16)); // 新容量通常为当前容量的两倍 385 | iterator newbegin = DataAllocator::Allocate(newsize); // 分配新内存 386 | iterator newend = newbegin; 387 | 388 | newend = uninitialized_copy(begin_, pos, newbegin); // 复制原有数据到新内存 389 | newend = uninitialized_fill_n(newend, nums, x); // 插入新元素 390 | newend = uninitialized_copy(pos, end_, newend); // 复制剩余数据到新内存 391 | 392 | DestroynDeallocate(begin_, end_, static_cast(capacity_ - begin_)); // 释放旧内存 393 | begin_ = newbegin; 394 | end_ = newend; 395 | capacity_ = begin_ + newsize; 396 | } 397 | ``` 398 | 399 | ### 5. 特别注意 400 | 401 | 在C++模板编程中,**模板重载冲突**是一个常见问题,尤其是在使用构造函数时。当设计`vector`类时,构造函数可能接受两个迭代器参数用于范围初始化,也可能接受一个长度和一个初始值进行初始化。这时编译器可能无法区分这两个重载,因为它们的参数类型相同,导致冲突。 402 | 403 | ### 5.1 模板重载冲突 404 | 405 | 例如,以下代码中的两个构造函数会产生冲突: 406 | 407 | ```cpp 408 | class vector { 409 | public: 410 | // 接受长度和初值的构造函数 411 | explicit vector(size_type len, const T& value) noexcept { NumsInit(len, value); } 412 | 413 | // 接受两个迭代器的构造函数 414 | template 415 | vector(Iterator first, Iterator last) noexcept { 416 | RangeInit(first, last); 417 | } 418 | }; 419 | 420 | // 示例 421 | int main() { 422 | vector v1(10, 5); // 编译器疑惑调用哪个构造函数 423 | vector v2(v1.begin(), v1.end()); // 编译器疑惑调用哪个构造函数 424 | } 425 | ``` 426 | 427 | 两个构造函数的参数形式相同(均为两个参数),编译器无法确定它们的具体用途。例如,当传递两个整数时,编译器无法区分这些整数是表示长度和初值,还是表示迭代器范围。这种情况会导致**模板重载歧义**,从而出现编译错误。为了解决这个问题,我们可以借助模板元编程与SFINAE机制。 428 | 429 | ### 5.2 基础的`IteratorTraits`实现 430 | 431 | 我们首先需要一种机制来区分迭代器和非迭代器。`IteratorTraits`提供了一种方法来提取类型的迭代器特征。首先,我们为所有非迭代器类型提供一个基础实现: 432 | 433 | ```cpp 434 | template 435 | class IteratorTraits {}; 436 | ``` 437 | 438 | - 这个实现适用于所有不符合迭代器特征的类型,**它提供默认的空实现,防止在处理非迭代器类型时出现编译错误**。 439 | 440 | ### 5.3 偏特化的`IteratorTraits`实现 441 | 442 | 接下来,我们通过**偏特化**为真正的迭代器类型提供特定实现: 443 | 444 | ```cpp 445 | template 446 | class IteratorTraits> { 447 | public: 448 | using IteratorCategory = typename T::iterator_category; 449 | }; 450 | ``` 451 | 452 | - 这里使用`void_t`是为了检测某个类型是否拥有`iterator_category`。`void_t`的原理是,当某个类型不包含所需的成员(如`iterator_category`),模板替换会失败,从而触发SFINAE机制,编译器不会报错,而是会选择基础模板版本。这使得我们可以有效区分迭代器和非迭代器类型。 453 | - `std::void_t` 是 C++17 引入的一个元编程工具,用于检测某些类型的有效性。它通过将任意数量的类型参数替换为 `void`,帮助我们检查这些类型是否存在而不产生编译错误。 454 | 455 | ### 5.4 `IsIterator`的实现 456 | 457 | 为了更高效地判断一个类型是否为迭代器,我们可以使用模板元编程结合SFINAE机制来实现`IsIterator`: 458 | 459 | ```cpp 460 | template 461 | class IsIterator { 462 | private: 463 | // 非迭代器类型匹配 464 | template 465 | static std::false_type test(...); 466 | 467 | // 迭代器类型匹配 468 | template 469 | static auto test(int) -> decltype(typename IteratorTraits::IteratorCategory(), std::true_type()); 470 | 471 | public: 472 | // 判断是否为迭代器类型 473 | static const bool value = decltype(test(0))::value; 474 | }; 475 | ``` 476 | 477 | **代码解读**: 478 | - `test(int)`分支通过`decltype`机制判断类型是否包含`IteratorCategory`,即是否为迭代器。如果是迭代器,返回`std::true_type`。 479 | - `test(...)`分支是一个捕获所有其他情况的泛化版本,当类型不是迭代器时返回`std::false_type`。 480 | - 最终,`decltype(test(0))::value`判断类型`T`是否为迭代器。 481 | - `test(int)` 是一种优先匹配的重载,它用于检测 `IteratorCategory` 是否存在。而 `test(...)` 是一个“兜底”方案,用于捕获所有其他情况。当类型不包含 `IteratorCategory` 时,`test(...)` 会被调用。 482 | 483 | ### 5.5 使用`std::enable_if`结合`IsIterator` 484 | 485 | 在构造函数中,我们可以利用`std::enable_if`和`IsIterator`来区分迭代器和其他类型,确保只有传入迭代器时,才会实例化迭代器构造函数: 486 | 487 | ```cpp 488 | template::value, int>::type = 0> 489 | vector(Iterator first, Iterator last) noexcept { 490 | RangeInit(first, last); 491 | } 492 | ``` 493 | 494 | - `std::enable_if::value, int>::type = 0`确保了只有在`Iterator`是迭代器类型时,才会启用这个重载版本,从而避免与其他构造函数产生冲突。 495 | 496 | ### 5.6 SFINAE机制 497 | 498 | 这个方案依赖于C++中的**SFINAE**(Substitution Failure Is Not An Error)机制。SFINAE意味着当模板参数替换失败时,编译器不会报错,而是选择另一个匹配的模板重载。这使得我们可以编写出更灵活、通用的代码,并有效避免模板重载冲突。 499 | 500 | 通过这些技巧,我们可以确保`vector`类的构造函数在不同参数类型下都能正确匹配: 501 | 502 | ```cpp 503 | int main() { 504 | vector v1(10, 5); // 匹配长度和初值的构造函数 505 | vector v2(v1.begin(), v1.end()); // 匹配迭代器范围的构造函数 506 | } 507 | ``` 508 | --------------------------------------------------------------------------------