个人观点:
在实现相应的迭代器时,我们多传入了两个模版参数来实现iterator和const_iterator的代码复用
这使用了泛型编程的抽象差异思想:
当我们的两个实现有很多相似重复的地方,而差异仅在一些返回值或者执行方式时,我们可以考虑这种方式来实现代码复用--->引入泛型模版来来掩盖差异--->当传入不同的模版时我们就会出现不同的实现
讲解
简单来说,引入 Ref 和 Ptr 这两个额外模板参数,是为了把"返回值类型"当作参数传进去 ,从而用同一份代码模版,同时生成"普通迭代器"和"Const 迭代器"。
1. 为什么要这么做?
如果没有这两个额外的参数,为了实现 iterator(可读写)和 const_iterator(只读),你需要写两个几乎一模一样的类。
我们来看一下如果不使用模板技巧,代码会变成什么样(冗余写法):
// 1. 普通迭代器:允许修改数据
template<class T>
struct Iterator {
Node<T>* _node;
// 返回 T&,允许修改
T& operator*() { return _node->_val; }
// 返回 T*,允许修改
T* operator->() { return &_node->_val; }
};
// 2. Const迭代器:不允许修改数据
template<class T>
struct ConstIterator {
Node<T>* _node; // 这里的指针本身还是普通指针,因为迭代器要能跑
// 【区别在这里】:返回 const T&
const T& operator*() { return _node->_val; }
// 【区别在这里】:返回 const T*
const T* operator->() { return &_node->_val; }
};
发现: 这两个类,除了 operator* 和 operator-> 的返回类型 不同(一个是 T&/T*,一个是 const T&/const T*),其他代码(构造函数、++、--、!=、==)是完全一样的!
复制粘贴会导致代码维护困难:如果你修改了 Iterator 的逻辑,还得记得去改 ConstIterator。
2. 解决方案:把"差异"抽象成参数
为了复用代码,我们把变化的返回类型提取出来,作为模板参数传给迭代器类。
我们需要的不仅仅是 T(数据类型),还需要告诉编译器:
- 解引用
*时,返回什么类型?(是T&还是const T&?)------ 这就是 Ref - 箭头
->时,返回什么类型?(是T*还是const T*?)------ 这就是 Ptr
3. 代码实现:一石二鸟
现在,我们可以只写一个类模板:
// template<数据类型, 引用的返回类型, 指针的返回类型>
template<class T, class Ref, class Ptr>
struct ListIterator {
typedef ListIterator<T, Ref, Ptr> Self;
Node<T>* _node;
// ... 构造函数等 ...
// 使用 Ref 参数作为返回类型
Ref operator*() {
return _node->_val;
}
// 使用 Ptr 参数作为返回类型
Ptr operator->() {
return &_node->_val;
}
Self& operator++() {
_node = _node->_next;
return *this;
}
bool operator!=(const Self& s) {
return _node != s._node;
}
};
4. 如何使用?(在容器类中)
在你的 list 或 vector 类中,你只需要通过传递不同的参数来定义两种迭代器:
template<class T>
class list {
public:
typedef Node<T> Node;
// 1. 普通迭代器:传入 T& 和 T*
// Ref = T&, Ptr = T*
typedef ListIterator<T, T&, T*> iterator;
// 2. Const迭代器:传入 const T& 和 const T*
// Ref = const T&, Ptr = const T*
typedef ListIterator<T, const T&, const T*> const_iterator;
// ... begin() 和 end() ...
};
5. 总结
为什么除了 class T 还需要 Ref 和 Ptr?
- class T:决定了节点里存的是
int还是string。 - class Ref:决定了
*it返回的是可读写引用 (T&) 还是只读引用 (const T&)。 - class Ptr:决定了
it->返回的是可读写指针 (T*) 还是只读指针 (const T*)。
通过这三个参数的组合,编译器会在编译时自动为你生成两份代码,一份用于普通访问,一份用于只读访问,既保证了类型安全(const 正确性),又避免了写两遍重复的逻辑。这是 C++ STL 实现中非常精妙且标准的一个技巧。