STL中设计模式(一)
1. 迭代器模式
核心思想:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
STL 中的体现 :这是 STL 的灵魂。STL 将算法与容器分离,而连接二者的桥梁就是迭代器。无论是 vector、list 还是 map,只要你提供 begin() 和 end(),std::sort 或 std::find 就能无视底层数据结构(连续内存、链表节点、红黑树)进行遍历。
c++
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
// 一个泛型算法,只依赖迭代器接口,完全不知道底层是什么容器
template<typename Iter>
void printElements(Iter begin, Iter end) {
// 统一的迭代器遍历方式:*it 解引用,++it 前进
for (Iter it = begin; it != end; ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5}; // 连续内存
std::list<int> lst = {10, 20, 30, 40, 50}; // 链表节点
// 相同的算法代码,作用于完全不同的底层数据结构
std::cout << "Vector: ";
printElements(vec.begin(), vec.end());
std::cout << "List: ";
printElements(lst.begin(), lst.end());
// STL 算法也是纯粹基于迭代器工作
std::find(vec.begin(), vec.end(), 3);
return 0;
}
2. 适配器模式
核心思想:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
STL 中的体现:
- 容器适配器 :
stack、queue、priority_queue。它们本身不存储数据,而是封装了底层的容器(如deque或vector),屏蔽了底层容器的非标准接口,只暴露特定的语义(如栈的 LIFO)。 - 迭代器适配器 :
reverse_iterator、back_insert_iterator。例如back_inserter将普通的赋值操作=适配成底层的push_back()调用。
代码示例(容器适配器与迭代器适配器):
c++
#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
// 1. 容器适配器:stack 默认底层使用 deque,这里显式指定底层使用 vector
// stack 将 vector 的 push_back 适配成了 push,back 适配成了 top
std::stack<int, std::vector<int>> s;
s.push(10);
s.push(20);
s.push(30);
std::cout << "Stack top: " << s.top() << std::endl; // 输出 30
// 2. 迭代器适配器:back_inserter
std::vector<int> source = {1, 2, 3};
std::vector<int> dest;
// 如果直接 copy,dest 为空会越界。
// back_inserter 适配了赋值操作,每次赋值时自动调用 dest.push_back()
std::copy(source.begin(), source.end(), std::back_inserter(dest));
for(int n : dest) std::cout << n << " "; // 输出 1 2 3
return 0;
}
3. 策略模式
核心思想:定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。策略模式使得算法的变化不会影响到使用算法的客户。
STL 中的体现 :STL 并不使用虚函数来实现策略,而是利用模板参数在编译时注入策略。最典型的就是排序算法的自定义比较规则(仿函数或 Lambda 表达式),以及容器的分配器和哈希函数。
代码示例:
c++
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <functional>
int main() {
std::vector<int> vec = {5, 2, 9, 1, 5, 6};
// 策略 1:默认升序策略 (std::less<int>)
std::sort(vec.begin(), vec.end(), std::less<int>());
for(int n : vec) std::cout << n << " ";
std::cout << std::endl;
// 策略 2:降序策略 (std::greater<int>)
std::sort(vec.begin(), vec.end(), std::greater<int>());
for(int n : vec) std::cout << n << " ";
std::cout << std::endl;
// 策略 3:自定义策略 (Lambda 表达式:按个位数大小排序)
std::sort(vec.begin(), vec.end(), [](int a, int b) {
return (a % 10) < (b % 10);
});
for(int n : vec) std::cout << n << " ";
std::cout << std::endl;
// map 的策略注入:key 的比较策略
// 默认是 std::less<Key>,这里换成降序的 map
std::map<int, std::string, std::greater<int>> desc_map;
return 0;
}
4. 代理模式
核心思想:为其他对象提供一种代理以控制对这个对象的访问。
STL 中的体现 :智能指针 std::unique_ptr 和 std::shared_ptr。它们是原始指针的代理,提供了与原始指针完全相同的解引用接口(* 和 ->),但在底层接管了内存的生命周期管理(自动析构释放内存),这是典型的智能代理。
代码示例:
c++
#include <iostream>
#include <memory>
class HeavyObject {
public:
HeavyObject() { std::cout << "HeavyObject created\n"; }
~HeavyObject() { std::cout << "HeavyObject destroyed\n"; }
void doWork() { std::cout << "Working...\n"; }
};
int main() {
// unique_ptr 是原始指针的代理
// 对外暴露 * 和 -> 操作,像真指针一样使用
// 对内代理了内存的销毁逻辑
std::unique_ptr<HeavyObject> proxy(new HeavyObject());
// 使用代理对象,体验与真实对象无异
proxy->doWork();
(*proxy).doWork();
// 离开作用域,代理自动清理真实对象
return 0;
}
5. 命令模式
核心思想:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
STL 中的体现 :std::function 和 std::packaged_task。它们将各种可调用实体(函数、Lambda、仿函数、成员函数)包装成统一的类型擦除对象,可以存储、传递、排队执行,这是标准的命令模式应用。
代码示例:
C++
#include <iostream>
#include <vector>
#include <functional>
#include <string>
// 命令调度器,持有命令队列
class CommandDispatcher {
std::vector<std::function<void()>> command_queue;
public:
// 接受任何可调用对象,封装为统一的 std::function 命令
void addCommand(std::function<void()> cmd) {
command_queue.push_back(cmd);
}
void executeAll() {
for (auto& cmd : command_queue) {
cmd(); // 执行命令
}
command_queue.clear();
}
};
int main() {
CommandDispatcher dispatcher;
// 封装不同的命令(Lambda 表达式)
dispatcher.addCommand([]() { std::cout << "Command 1: Load data\n"; });
dispatcher.addCommand([]() { std::cout << "Command 2: Process data\n"; });
std::string data = "Result";
// 捕获上下文的命令
dispatcher.addCommand([&data]() { std::cout << "Command 3: Save " << data << "\n"; });
// 延迟执行、批量执行
dispatcher.executeAll();
return 0;
}
6. 模板方法模式
核心思想:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
STL 中的体现 :在泛型编程中,模板方法模式通常通过 CRTP(奇异递归模板模式) 来实现编译时的静态多态。STL 的 std::iterator (C++17 前) 的五类迭代器标签体系,以及分配器的特性提取,都利用了这种编译期骨架与步骤延迟的思想。
注:这里用现代 C++ 常用的 CRTP 演示 STL 风格的模板方法,因为纯 STL 源码中的 CRTP 过于底层。
代码示例 (CRTP 实现的静态模板方法):
C++
#include <iostream>
// 基类:定义算法骨架(模板方法),将具体步骤延迟到派生类
template <typename Derived>
class DataProcessor {
public:
// 模板方法:定义了处理的骨架
void process() {
static_cast<Derived*>(this)->readData();
static_cast<Derived*>(this)->compute();
static_cast<Derived*>(this)->writeData();
}
// 默认实现(可选)
void readData() { std::cout << "[Default] Reading generic data\n"; }
void compute() { std::cout << "[Default] Computing generic\n"; }
void writeData(){ std::cout << "[Default] Writing generic data\n"; }
};
// 具体类:覆写特定步骤
class ImageProcessor : public DataProcessor<ImageProcessor> {
public:
// 覆写 compute 步骤,readData 和 writeData 继承默认骨架
void compute() {
std::cout << "[Image] Applying Gaussian Blur\n";
}
};
int main() {
ImageProcessor imgProc;
// 调用从基类继承来的模板方法骨架,内部调用自己的 compute
imgProc.process();
return 0;
}