第31章 自定义和扩展标准库
标准库的设计原则
C++标准库的设计遵循以下原则:
- 可扩展性:标准库设计为可扩展的,允许用户自定义和扩展其功能
- 泛型编程:使用模板实现泛型,提高代码重用性
- 迭代器范围:使用半开区间
[begin, end) 作为标准接口 - 策略模式:通过模板参数允许用户自定义行为
- 命名约定:遵循一致的命名约定,如小写字母加下划线
- 异常安全:提供不同级别的异常安全性
扩展标准库容器
自定义容器
创建符合标准库风格的自定义容器,需要实现以下接口:
- 迭代器:提供
begin() 和 end() 方法,返回迭代器 - 容量管理:提供
size(), empty() 等方法 - 元素访问:提供
operator[], at() 等方法 - 修改操作:提供
insert(), erase(), clear() 等方法 - 分配器支持:支持自定义分配器
示例:自定义栈容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| template<typename T, typename Allocator = std::allocator<T>> class Stack { public: using value_type = T; using allocator_type = Allocator; using reference = value_type&; using const_reference = const value_type&; using size_type = std::size_t; using difference_type = std::ptrdiff_t; Stack() = default; explicit Stack(const Allocator& alloc) : data(alloc) {} bool empty() const noexcept { return data.empty(); } size_type size() const noexcept { return data.size(); } reference top() { if (empty()) { throw std::out_of_range("Stack is empty"); } return data.back(); } const_reference top() const { if (empty()) { throw std::out_of_range("Stack is empty"); } return data.back(); } void push(const value_type& value) { data.push_back(value); } void push(value_type&& value) { data.push_back(std::move(value)); } template<typename... Args> void emplace(Args&&... args) { data.emplace_back(std::forward<Args>(args)...); } void pop() { if (empty()) { throw std::out_of_range("Stack is empty"); } data.pop_back(); } void swap(Stack& other) noexcept { data.swap(other.data); } private: std::vector<T, Allocator> data; };
template<typename T, typename Allocator> void swap(Stack<T, Allocator>& lhs, Stack<T, Allocator>& rhs) noexcept { lhs.swap(rhs); }
|
扩展现有容器
通过继承或组合扩展现有容器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| template<typename T> class ExtendedVector { public: void push_back(const T& value) { data.push_back(value); } void push_back(T&& value) { data.push_back(std::move(value)); } void push_front(const T& value) { data.insert(data.begin(), value); } void push_front(T&& value) { data.insert(data.begin(), std::move(value)); } private: std::vector<T> data; };
|
自定义迭代器
迭代器的概念
C++标准库定义了五种迭代器类别:
- 输入迭代器:只读,单向移动
- 输出迭代器:只写,单向移动
- 前向迭代器:可读写,单向移动,可重复遍历
- 双向迭代器:可读写,双向移动
- 随机访问迭代器:可读写,随机访问
实现自定义迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| template<typename T> class Node { public: T value; Node* next; Node(const T& val) : value(val), next(nullptr) {} };
template<typename T> class LinkedList { public: class Iterator { public: using iterator_category = std::forward_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T& Iterator(Node<T>* node) : current(node) {} reference operator*() const { return current->value; } pointer operator->() const { return ¤t->value; } Iterator& operator++() { current = current->next; return *this; } Iterator operator++(int) { Iterator temp = *this; current = current->next; return temp; } bool operator==(const Iterator& other) const { return current == other.current; } bool operator!=(const Iterator& other) const { return current != other.current; } private: Node<T>* current; }; LinkedList() : head(nullptr), tail(nullptr) {} void push_back(const T& value) { Node<T>* newNode = new Node<T>(value); if (!head) { head = tail = newNode; } else { tail->next = newNode; tail = newNode; } } Iterator begin() const { return Iterator(head); } Iterator end() const { return Iterator(nullptr); } private: Node<T>* head; Node<T>* tail; };
int main() { LinkedList<int> list; list.push_back(1); list.push_back(2); list.push_back(3); for (int value : list) { std::cout << value << " "; } std::cout << std::endl; return 0; }
|
扩展标准库算法
自定义算法
创建符合标准库风格的自定义算法,需要遵循以下原则:
- 使用迭代器范围:接受
[begin, end) 迭代器范围 - 模板化:使用模板参数支持不同类型
- 命名约定:遵循标准库命名约定
- 异常安全:提供适当的异常安全性
示例:自定义算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| template<typename ForwardIt> auto minmax_element(ForwardIt first, ForwardIt last) { using value_type = typename std::iterator_traits<ForwardIt>::value_type; if (first == last) { return std::make_pair(last, last); } ForwardIt min_it = first; ForwardIt max_it = first; ++first; for (; first != last; ++first) { if (*first < *min_it) { min_it = first; } else if (*first > *max_it) { max_it = first; } } return std::make_pair(min_it, max_it); }
int main() { std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6}; auto [min_it, max_it] = minmax_element(v.begin(), v.end()); std::cout << "Min: " << *min_it << ", Max: " << *max_it << std::endl; return 0; }
|
算法适配器
创建算法适配器,扩展现有算法的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| template<typename InputIt, typename UnaryFunction> UnaryFunction for_each_if(InputIt first, InputIt last, UnaryFunction f, std::function<bool(const typename std::iterator_traits<InputIt>::value_type&)> pred) { for (; first != last; ++first) { if (pred(*first)) { f(*first); } } return f; }
int main() { std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for_each_if(v.begin(), v.end(), [](int n) { std::cout << n << " "; }, [](int n) { return n % 2 == 0; }); std::cout << std::endl; return 0; }
|
自定义分配器
分配器的概念
分配器是标准库中用于内存管理的组件,它提供以下功能:
- 内存分配:分配原始内存
- 内存释放:释放分配的内存
- 对象构造:在分配的内存中构造对象
- 对象析构:析构对象但不释放内存
实现自定义分配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| template<typename T> class CustomAllocator { public: using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using propagate_on_container_move_assignment = std::true_type; CustomAllocator() noexcept = default; template<typename U> CustomAllocator(const CustomAllocator<U>&) noexcept {} T* allocate(size_type n) { if (n > max_size()) { throw std::bad_alloc(); } return static_cast<T*>(std::malloc(n * sizeof(T))); } void deallocate(T* p, size_type) noexcept { std::free(p); } size_type max_size() const noexcept { return std::numeric_limits<size_type>::max() / sizeof(T); } template<typename U, typename... Args> void construct(U* p, Args&&... args) { ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...); } template<typename U> void destroy(U* p) { p->~U(); } };
template<typename T, typename U> bool operator==(const CustomAllocator<T>&, const CustomAllocator<U>&) noexcept { return true; }
template<typename T, typename U> bool operator!=(const CustomAllocator<T>&, const CustomAllocator<U>&) noexcept { return false; }
int main() { std::vector<int, CustomAllocator<int>> v; v.push_back(1); v.push_back(2); v.push_back(3); for (int i : v) { std::cout << i << " "; } std::cout << std::endl; return 0; }
|
扩展标准库流
自定义流缓冲区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| class CustomStreambuf : public std::streambuf { private: std::vector<char> buffer; static constexpr std::size_t buffer_size = 256; public: CustomStreambuf() { buffer.resize(buffer_size); setp(buffer.data(), buffer.data() + buffer.size() - 1); } protected: int_type overflow(int_type c) override { if (c != traits_type::eof()) { *pptr() = static_cast<char>(c); pbump(1); if (sync() == -1) { return traits_type::eof(); } } return c; } int sync() override { if (pbase() != pptr()) { std::cout.write(pbase(), pptr() - pbase()); std::cout.flush(); setp(buffer.data(), buffer.data() + buffer.size() - 1); } return 0; } };
class CustomOutputStream : public std::ostream { private: CustomStreambuf streambuf; public: CustomOutputStream() : std::ostream(&streambuf) {} };
int main() { CustomOutputStream out; out << "Hello, Custom Stream!" << std::endl; return 0; }
|
自定义流操纵符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| std::ostream& hexify(std::ostream& os) { return os << std::hex << std::showbase; }
std::ostream& resetify(std::ostream& os) { return os << std::dec << std::noshowbase; }
int main() { int value = 255; std::cout << "Decimal: " << value << std::endl; std::cout << "Hex: " << hexify << value << resetify << std::endl; return 0; }
|
自定义函子和函数对象
标准库中的函数对象
标准库提供了多种函数对象,如:
- 算术运算:
std::plus, std::minus, std::multiplies 等 - 比较运算:
std::equal_to, std::greater, std::less 等 - 逻辑运算:
std::logical_and, std::logical_or, std::logical_not 等 - 函数适配器:
std::bind, std::mem_fn 等
自定义函数对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| struct IsEven { bool operator()(int n) const { return n % 2 == 0; } };
struct Square { template<typename T> T operator()(T value) const { return value * value; } };
int main() { std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; auto count = std::count_if(v.begin(), v.end(), IsEven()); std::cout << "Even numbers: " << count << std::endl; std::vector<int> squared(v.size()); std::transform(v.begin(), v.end(), squared.begin(), Square()); std::cout << "Squared: "; for (int n : squared) { std::cout << n << " "; } std::cout << std::endl; return 0; }
|
扩展标准库的策略模式
策略模式的应用
标准库中广泛使用策略模式,如:
- 排序算法:
std::sort 允许自定义比较策略 - 容器:
std::set 允许自定义排序策略 - 分配器:所有容器都允许自定义分配策略
- 哈希表:
std::unordered_map 允许自定义哈希策略
示例:自定义哈希策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| struct StringHash { std::size_t operator()(const std::string& s) const { std::size_t hash = 0; for (char c : s) { hash = hash * 31 + static_cast<unsigned char>(c); } return hash; } };
struct StringEqual { bool operator()(const std::string& lhs, const std::string& rhs) const { return lhs == rhs; } };
using CustomHashMap = std::unordered_map<std::string, int, StringHash, StringEqual>;
int main() { CustomHashMap map; map["one"] = 1; map["two"] = 2; map["three"] = 3; for (const auto& pair : map) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; }
|
自定义类型特征
类型特征的概念
类型特征(Type Traits)是标准库中用于编译时类型信息查询和转换的工具,定义在 <type_traits> 头文件中。
实现自定义类型特征
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| template<typename T> struct is_pointer { static constexpr bool value = false; };
template<typename T> struct is_pointer<T*> { static constexpr bool value = true; };
template<typename T> inline constexpr bool is_pointer_v = is_pointer<T>::value;
int main() { std::cout << "is_pointer<int>: " << is_pointer_v<int> << std::endl; std::cout << "is_pointer<int*>: " << is_pointer_v<int*> << std::endl; return 0; }
|
类型特征的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| template<typename T> void process_impl(const T& value, std::true_type) { std::cout << "Processing pointer: " << *value << std::endl; }
template<typename T> void process_impl(const T& value, std::false_type) { std::cout << "Processing value: " << value << std::endl; }
template<typename T> void process(const T& value) { process_impl(value, std::is_pointer<T>{}); }
int main() { int x = 42; int* px = &x; process(x); process(px); return 0; }
|
扩展标准库的最佳实践
1. 遵循标准库风格
- 使用与标准库一致的命名约定
- 遵循标准库的接口设计,如迭代器范围
- 使用与标准库一致的异常处理策略
- 提供与标准库一致的文档
2. 测试与标准库兼容性
- 确保自定义组件与标准库算法兼容
- 测试不同类型的输入和边界情况
- 确保异常安全性符合预期
3. 性能考虑
- 优化自定义组件的性能,与标准库组件相当
- 避免不必要的内存分配和拷贝
- 考虑缓存局部性
4. 可维护性
- 保持代码简洁明了
- 添加适当的注释和文档
- 遵循良好的代码组织
5. 避免修改标准库
- 不要修改标准库的头文件或实现
- 不要依赖标准库的内部实现细节
- 使用继承或组合扩展标准库功能
示例:完整的自定义容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
| template<typename T, typename Allocator = std::allocator<T>> class CircularBuffer { public: using value_type = T; using allocator_type = Allocator; using reference = value_type&; using const_reference = const value_type&; using pointer = typename std::allocator_traits<Allocator>::pointer; using const_pointer = typename std::allocator_traits<Allocator>::const_pointer; using size_type = std::size_t; using difference_type = std::ptrdiff_t; class Iterator { public: using iterator_category = std::random_access_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T& Iterator(CircularBuffer* buffer, size_type index) : buffer(buffer), index(index) {} reference operator*() const { return buffer->data[(buffer->start + index) % buffer->capacity_]; } pointer operator->() const { return &buffer->data[(buffer->start + index) % buffer->capacity_]; } reference operator[](difference_type n) const { return buffer->data[(buffer->start + index + n) % buffer->capacity_]; } Iterator& operator++() { ++index; return *this; } Iterator operator++(int) { Iterator temp = *this; ++index; return temp; } Iterator& operator--() { --index; return *this; } Iterator operator--(int) { Iterator temp = *this; --index; return temp; } Iterator& operator+=(difference_type n) { index += n; return *this; } Iterator operator+(difference_type n) const { Iterator temp = *this; return temp += n; } Iterator& operator-=(difference_type n) { index -= n; return *this; } Iterator operator-(difference_type n) const { Iterator temp = *this; return temp -= n; } difference_type operator-(const Iterator& other) const { return index - other.index; } bool operator==(const Iterator& other) const { return buffer == other.buffer && index == other.index; } bool operator!=(const Iterator& other) const { return !(*this == other); } bool operator<(const Iterator& other) const { return index < other.index; } bool operator<=(const Iterator& other) const { return index <= other.index; } bool operator>(const Iterator& other) const { return index > other.index; } bool operator>=(const Iterator& other) const { return index >= other.index; } private: CircularBuffer* buffer; size_type index; }; using iterator = Iterator; using const_iterator = Iterator; using reverse_iterator = std::reverse_iterator<iterator>; using const_reverse_iterator = std::reverse_iterator<const_iterator>; explicit CircularBuffer(size_type capacity = 16, const Allocator& alloc = Allocator()) : allocator(alloc), capacity_(capacity), size_(0), start(0), end(0) { data = std::allocator_traits<Allocator>::allocate(allocator, capacity_); } ~CircularBuffer() { for (size_type i = 0; i < size_; ++i) { std::allocator_traits<Allocator>::destroy(allocator, &data[(start + i) % capacity_]); } std::allocator_traits<Allocator>::deallocate(allocator, data, capacity_); } bool empty() const noexcept { return size_ == 0; } size_type size() const noexcept { return size_; } size_type capacity() const noexcept { return capacity_; } void reserve(size_type new_capacity) { if (new_capacity <= capacity_) { return; } pointer new_data = std::allocator_traits<Allocator>::allocate(allocator, new_capacity); for (size_type i = 0; i < size_; ++i) { std::allocator_traits<Allocator>::construct(allocator, &new_data[i], std::move(data[(start + i) % capacity_])); std::allocator_traits<Allocator>::destroy(allocator, &data[(start + i) % capacity_]); } std::allocator_traits<Allocator>::deallocate(allocator, data, capacity_); data = new_data; capacity_ = new_capacity; start = 0; end = size_; } reference front() { if (empty()) { throw std::out_of_range("CircularBuffer is empty"); } return data[start]; } const_reference front() const { if (empty()) { throw std::out_of_range("CircularBuffer is empty"); } return data[start]; } reference back() { if (empty()) { throw std::out_of_range("CircularBuffer is empty"); } return data[(end - 1) % capacity_]; } const_reference back() const { if (empty()) { throw std::out_of_range("CircularBuffer is empty"); } return data[(end - 1) % capacity_]; } reference operator[](size_type n) { if (n >= size_) { throw std::out_of_range("Index out of range"); } return data[(start + n) % capacity_]; } const_reference operator[](size_type n) const { if (n >= size_) { throw std::out_of_range("Index out of range"); } return data[(start + n) % capacity_]; } reference at(size_type n) { return operator[](n); } const_reference at(size_type n) const { return operator[](n); } iterator begin() noexcept { return iterator(this, 0); } const_iterator begin() const noexcept { return const_iterator(const_cast<CircularBuffer*>(this), 0); } iterator end() noexcept { return iterator(this, size_); } const_iterator end() const noexcept { return const_iterator(const_cast<CircularBuffer*>(this), size_); } reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } reverse_iterator rend() noexcept { return reverse_iterator(begin()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } void push_back(const value_type& value) { if (size_ == capacity_) { reserve(capacity_ * 2); } std::allocator_traits<Allocator>::construct(allocator, &data[end], value); end = (end + 1) % capacity_; ++size_; } void push_back(value_type&& value) { if (size_ == capacity_) { reserve(capacity_ * 2); } std::allocator_traits<Allocator>::construct(allocator, &data[end], std::move(value)); end = (end + 1) % capacity_; ++size_; } template<typename... Args> void emplace_back(Args&&... args) { if (size_ == capacity_) { reserve(capacity_ * 2); } std::allocator_traits<Allocator>::construct(allocator, &data[end], std::forward<Args>(args)...); end = (end + 1) % capacity_; ++size_; } void pop_front() { if (empty()) { throw std::out_of_range("CircularBuffer is empty"); } std::allocator_traits<Allocator>::destroy(allocator, &data[start]); start = (start + 1) % capacity_; --size_; } void clear() noexcept { for (size_type i = 0; i < size_; ++i) { std::allocator_traits<Allocator>::destroy(allocator, &data[(start + i) % capacity_]); } size_ = 0; start = 0; end = 0; } private: Allocator allocator; pointer data; size_type capacity_; size_type size_; size_type start; size_type end; };
int main() { CircularBuffer<int> buf(5); for (int i = 1; i <= 10; ++i) { buf.push_back(i); } std::cout << "Elements: "; for (int n : buf) { std::cout << n << " "; } std::cout << std::endl; std::cout << "Front: " << buf.front() << std::endl; std::cout << "Back: " << buf.back() << std::endl; std::cout << "Element at 2: " << buf[2] << std::endl; buf.pop_front(); std::cout << "After pop_front: "; for (int n : buf) { std::cout << n << " "; } std::cout << std::endl; return 0; }
|
总结
自定义和扩展标准库是C++编程中的重要技能,它允许我们:
- 扩展标准库功能:为标准库添加新的功能和组件
- 自定义行为:根据特定需求自定义标准库的行为
- 提高代码重用性:创建可重用的组件,遵循标准库的设计原则
- 优化性能:为特定场景创建高性能的实现
在自定义和扩展标准库时,应该遵循标准库的设计原则,确保与标准库的兼容性,同时考虑性能和可维护性。通过合理地自定义和扩展标准库,我们可以创建更加灵活、高效、可维护的C++代码。