一、背景
用上 STL 后,代码就像搭积木一样,又快又好。算法就像精密的零件,数据在优雅的容器中流动。直到有一天,你遇到一份古老的代码,把你拖回原始 for
循环的泥潭。
旧代码的接口,让 STL 的算法施展不开。该如何把旧代码关进 STL 的笼子里。本文来告诉你如何让 STL 算法与那些“不听话”的老代码共舞。
简单说,就是如何让 STL 算法,能把结果输出到不兼容 STL 的旧结构里。
二、一个简单的例子
为了方便理解,简化一下问题:
假设有一堆输入数据,放在 std::vector<Input> inputs
里。我们需要对每个输入,应用一个函数 Output f(Input const& input)
,然后把结果保存到一个叫 legacyRepository
的老式结构中。这个结构有个添加数据的函数 void addInRepository(Output const& value, LegacyRepository& legacyRepository)
,但它不是 STL 容器,用起来很麻烦。
理想情况下,可以直接用 std::transform
和 std::back_inserter
把结果保存到 std::vector
里。但如果没法修改 legacyRepository
,该怎么办呢?
// 如果可以用 std::vector,事情就简单了
std::transform(begin(inputs), end(inputs), std::back_inserter(repository), f);
三、模仿 std::back_inserter
std::back_inserter
给了灵感。它把数据插入到 std::vector
中,本质上是一个输出迭代器 std::back_insert_iterator
。关键在于它的 operator=
,它负责真正把数据放到容器里:
// (理论代码,用于说明 std::back_inserter 的工作方式)
back_insert_iterator& operator=(T const& value) {
container_.push_back(value);
return *this;
}
它能工作是因为 operator*
并没有返回容器的元素,而是把控制权留给了迭代器自己。operator++
啥也不干。
这种方法在有 push_back
的容器上很好用。但能不能把它推广到其他接口上呢?
3.1、定制插入的利器
于是,自定义迭代器: custom_insert_iterator
。它不依赖于特定的容器,而是接受一个自定义的函数(或者函数对象),来替代 push_back
:
template<typename OutputInsertFunction>
class custom_insert_iterator {
public:
using iterator_category = std::output_iterator_tag;
explicit custom_insert_iterator(OutputInsertFunction insertFunction) : insertFunction_(insertFunction) {}
custom_insert_iterator& operator++(){ return *this; }
custom_insert_iterator& operator*(){ return *this; }
template<typename T>
custom_insert_iterator& operator=(T const& value) {
insertFunction_(value);
return *this;
}
private:
OutputInsertFunction insertFunction_;
};
为了使用更方便,写一个辅助函数 custom_inserter
:
template <typename OutputInsertFunction>
custom_insert_iterator<OutputInsertFunction> custom_inserter(OutputInsertFunction insertFunction) {
return custom_insert_iterator<OutputInsertFunction>(insertFunction);
}
现在,可以这样用它了:
// 简单粗暴版
std::copy(begin(inputs), end(inputs),
custom_inserter([&legacyRepository](Output const& value){ addInRepository(value, legacyRepository); }));
// 优雅版
auto insertInRepository(LegacyRepository& legacyRepository) {
return [&legacyRepository](Output const& value) {
addInRepository(value, legacyRepository);
};
}
std::transform(begin(inputs), end(inputs), custom_inserter(insertInRepository(legacyRepository)));
3.2、更简单的选择?
如果只是简单的循环,直接用 for
循环更方便,比如这样:
// 简单的例子,用 for 循环更简单
for (const auto& input: inputs) addInRepository(f(input), lecgacyRepository);
但是,当需要用到更复杂的 STL 算法时,custom_inserter
的优势就体现出来了,比如:
// 当使用复杂的 STL 算法时,custom_inserter 的优势就体现出来了
std::set_difference(begin(inputs1), end(inputs1),
begin(inputs2), end(inputs2),
custom_inserter(insertInRepository(legacyRepository)));
四、新旧之争
这并没有减少遗留代码,只是增加了一个非标准的组件。那么,这样做值得吗?
这就需要权衡了。如果能彻底替换遗留代码,用上 STL 兼容的容器,那当然是最好的选择。但如果做不到,或者成本太高,那么我们就要在放弃 STL 算法,或者使用 custom_inserter
之间做出选择。虽然 custom_inserter
不是标准组件,有一定的间接性,但它可以让你在老代码中,也能享受到 STL 的便利。
这里贴一下完整代码:
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator> // for std::output_iterator_tag
// 假设的输入输出类型和旧代码结构
struct Input {
int value;
};
struct Output {
int result;
};
struct LegacyRepository {
// 老代码仓库,不兼容 STL
void store(const Output& output) {
std::cout << "Storing result: " << output.result << std::endl;
}
};
// 模拟一个对 Input 进行处理的函数
Output processInput(const Input& input) {
return {input.value * 2};
}
// 向 LegacyRepository 添加数据的函数 (需要适配的函数)
void addInRepository(const Output& value, LegacyRepository& legacyRepository) {
legacyRepository.store(value); // 调用旧代码的存储函数
}
// 自定义插入迭代器
template<typename OutputInsertFunction>
class custom_insert_iterator {
public:
using iterator_category = std::output_iterator_tag; // 标记为输出迭代器
// 构造函数,接收一个函数对象
explicit custom_insert_iterator(OutputInsertFunction insertFunction) : insertFunction_(insertFunction) {}
// 迭代器操作符(不做任何实际操作,仅为了满足迭代器接口)
custom_insert_iterator& operator++() { return *this; }
custom_insert_iterator& operator*() { return *this; }
// 赋值操作符,实际进行插入操作
template<typename T>
custom_insert_iterator& operator=(const T& value) {
insertFunction_(value); // 调用用户提供的插入函数
return *this;
}
private:
OutputInsertFunction insertFunction_; // 存储插入函数
};
// 辅助函数,用于创建 custom_insert_iterator,避免显式指定模板参数
template <typename OutputInsertFunction>
auto custom_inserter(OutputInsertFunction insertFunction) { // 使用 auto 推导返回类型
return custom_insert_iterator<OutputInsertFunction>(insertFunction);
}
// 示例函数,返回一个 Lambda,封装了向 LegacyRepository 插入数据的逻辑
auto insertInRepository(LegacyRepository& legacyRepository) {
return [&legacyRepository](const Output& value) {
addInRepository(value, legacyRepository); // 实际调用旧代码的添加函数
};
}
int main() {
std::vector<Input> inputs = {{1}, {2}, {3}, {4}, {5}};
LegacyRepository repository;
std::cout << "Using std::transform with custom_inserter:" << std::endl;
// 使用 std::transform 和 custom_inserter 将数据处理后插入 LegacyRepository
std::transform(inputs.begin(), inputs.end(),
custom_inserter(insertInRepository(repository)), // 使用 Lambda 表达式封装插入逻辑
processInput); // 输入数据并处理
// 或者使用 std::copy,需要先处理数据
std::cout << "\nUsing std::copy with custom_inserter (after processing):" << std::endl;
std::vector<Output> processedOutputs;
std::transform(inputs.begin(), inputs.end(), std::back_inserter(processedOutputs), processInput);
std::copy(processedOutputs.begin(), processedOutputs.end(), custom_inserter(insertInRepository(repository)));
// 示例:与 std::set_difference 结合使用
std::vector<Input> inputs2 = {{3}, {5}, {7}};
std::vector<Input> differenceResult;
std::set_difference(inputs.begin(), inputs.end(), inputs2.begin(), inputs2.end(), std::back_inserter(differenceResult),
[](const Input& a, const Input& b) { return a.value < b.value; });
std::cout << "\nUsing std::set_difference with custom_inserter:" << std::endl;
std::transform(differenceResult.begin(), differenceResult.end(), custom_inserter(insertInRepository(repository)), processInput);
return 0;
}
五、总结
自定义 custom_insert_iterator
类允许 STL 算法直接向老代码的存储函数写入数据,而无需修改现有代码结构。通过 custom_inserter
辅助函数和 Lambda 表达式,简化了迭代器的创建和使用,实现了 STL 算法与老代码的无缝集成。