泛型编程的差异抽象思想

个人观点:

在实现相应的迭代器时,我们多传入了两个模版参数来实现iterator和const_iterator的代码复用

这使用了泛型编程的抽象差异思想:

当我们的两个实现有很多相似重复的地方,而差异仅在一些返回值或者执行方式时,我们可以考虑这种方式来实现代码复用--->引入泛型模版来来掩盖差异--->当传入不同的模版时我们就会出现不同的实现

讲解

简单来说,引入 RefPtr 这两个额外模板参数,是为了把"返回值类型"当作参数传进去 ,从而用同一份代码模版,同时生成"普通迭代器"和"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(数据类型),还需要告诉编译器:

  1. 解引用 * 时,返回什么类型?(是 T& 还是 const T&?)------ 这就是 Ref
  2. 箭头 -> 时,返回什么类型?(是 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. 如何使用?(在容器类中)

在你的 listvector 类中,你只需要通过传递不同的参数来定义两种迭代器:

复制代码
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 还需要 RefPtr

  • class T:决定了节点里存的是 int 还是 string
  • class Ref:决定了 *it 返回的是可读写引用 (T&) 还是只读引用 (const T&)。
  • class Ptr:决定了 it-> 返回的是可读写指针 (T*) 还是只读指针 (const T*)。

通过这三个参数的组合,编译器会在编译时自动为你生成两份代码,一份用于普通访问,一份用于只读访问,既保证了类型安全(const 正确性),又避免了写两遍重复的逻辑。这是 C++ STL 实现中非常精妙且标准的一个技巧。

相关推荐
CheerWWW2 分钟前
深入理解计算机系统——位运算、树状数组
笔记·学习·算法·计算机系统
上海合宙LuatOS9 分钟前
LuatOS扩展库API——【exremotecam】网络摄像头控制
开发语言·网络·物联网·lua·luatos
中屹指纹浏览器13 分钟前
2026浏览器指纹检测技术演进与多账号反检测实战策略
经验分享·笔记
feng_you_ying_li15 分钟前
C++11,{}的初始化情况与左右值及其引用
开发语言·数据结构·c++
xiaotao13122 分钟前
JS new 操作符完整执行过程
开发语言·前端·javascript·原型模式
TE-茶叶蛋26 分钟前
结合登录页-PHP基础知识点解析
android·开发语言·php
无巧不成书021826 分钟前
Java包(package)全解:从定义、使用到避坑,新手零基础入门到实战
java·开发语言·package·java包
WangJunXiang642 分钟前
Python网络编程
开发语言·网络·python
guhy fighting44 分钟前
new Map,Array.from,Object.entries的作用以及使用方法
开发语言·前端·javascript
lsx20240644 分钟前
操作系统统计
开发语言