STL 源码剖析 总结索引 | 第三章:迭代器(iterators)概念与 traits 编程技法

iterator 模式定义如下:提供一种方法,使之能够 依序巡访某个聚合物(容器)所含的各个元素,而又 无需暴露 该聚合物的内部表述方式

1、迭代器设计思维——STL 关键所在

1、STL 的中心思想在于:将数据容器(containers)和算法(algorithms)分开,C++ 的类模板 和 函数模板 可分别达成目标

类模板 允许 定义一个通用的类,该类 可以在实例化时指定不同的数据类型

template <typename T>
class 类名 {
    
    
    // 使用T来表示通用的数据类型
    T 成员变量;
public:
    类名(T 参数) : 成员变量(参数) {
    
    }
    T get() const {
    
     return 成员变量; }
};

类名<int> 对象1(10);     // 使用int类型
类名<double> 对象2(3.14); // 使用double类型

函数模板 与类模板类似,但它是用于 定义可以处理不同类型数据的函数

template <typename T>
T 函数名(T 参数1, T 参数2) {
    
    
    return 参数1 + 参数2;
}

T 表示一个类型参数,函数可以 接受相同类型的参数 并返回相同类型的结果。调用时,编译器 会根据传入的参数类型 推断出T的类型

int a = 5, b = 10;
cout << 函数名(a, b) << endl; // 输出15

double x = 2.5, y = 3.5;
cout << 函数名(x, y) << endl; // 输出6.0

容器、算法、迭代器(iterator,扮演 粘胶角色,容器、算法之间的良好胶着剂)的合作展示

template <class InputIterator, class T>
InputIterator find(InputIterator first,
				InputIterator last,
				const T& value){
    
    
	while (first != last && *first != value)
		++first;
	return first;
}

只要 给予不同的迭代器,find() 便能够对不同的容器 进行查找操作

	const int arraySize = 7;
    int ia[arraySize] = {
    
    0,1,2,3,4,5,6 };

    vector<int> ivect(ia, ia + arraySize);
    list<int> ilist(ia, ia + arraySize);
    deque<int> ideque(ia, ia + arraySize); // 注意:VC6[x], 未符合标准

    vector<int>::iterator it1 = find(ivect.begin(), ivect.end(), 4);
    if (it1 == ivect.end())
        cout << "4 not found." << endl;
    else
        cout << "4 found. " << *it1 << endl; // 执行结果: 4 found. 4

    list<int>::iterator it2 = find(ilist.begin(), ilist.end(), 6);
    if (it2 == ilist.end())
        cout << "6 not found." << endl;
    else
        cout << "6 found. " << *it2 << endl; // 执行结果: 6 found. 6

    deque<int>::iterator it3 = find(ideque.begin(), ideque.end(), 8);
    if (it3 == ideque.end())
        cout << "8 not found." << endl;
    else
        cout << "8 found. " << *it3 << endl; // 执行结果: 8 not found

2、迭代器是一种智能指针

1、迭代器 是一种行为类似指针的对象,而指针的各种行为中 最常见也最重要的便是 解引用 和 成员访问,因此,迭代器最重要的编程工作 就是对 operator* 和 operator-> 进行重载工作

例如,auto_ptr 是一个用来包装原生指针(原生指针是C++中 直接指向内存地址的变量)的对象,内存漏洞问题(指程序 动态分配了内存,但 没有在使用完后正确释放,导致内存 无法被回收和重复利用)可藉此获得解决。auto_ptr 用法如下,和原生指针一模一样:

void func()
{
    
    
    auto_ptr<string> ps(new string("jjhou"));

    cout << *ps << endl;                // 输出: jjhou
    cout << ps->size() << endl;          // 输出: 5
    // 离开前不需 delete,auto_ptr 会自动释放内存
}

以算式 new 动态配置一个初值为 “jjhou” 的 string 对象,并将所得结果(一个原生指针)作为 auto_ptr<string> 对象的初值。注意,auto_ptr 角括号内 放的是 “原生指针所指对象” 的型别(即指针所指对象的类别)

template<class T> 
class auto_ptr {
    
    
public:
	explicit auto_ptr(T *p = 0) : pointee(p) {
    
    }
	template<class U> 
	auto_ptr(auto_ptr<U>& rhs) : pointee(rhs.release()) {
    
    } 
	// 拷贝构造函数,用于通过另一个 auto_ptr 对象来初始化当前对象
	~auto_ptr() {
    
     delete pointee; }

	template<class U> 
	auto_ptr<T>& operator=(auto_ptr<U>& rhs) {
    
    
		// 重载的赋值操作符 =,用于将一个 auto_ptr 赋值给另一个 auto_ptr
		if (this != &rhs) reset(rhs.release());// 赋值给自己没有意义
		return *this;
	}

	T& operator*( ) const {
    
     return *pointee; }
	T* operator->( ) const {
    
     return pointee; } 
	// 用于让 auto_ptr 对象能够像普通指针那样访问其所指向对象的成员
	
	T* get( ) const {
    
     return pointee; }
	// ...
private:
	T *pointee;
}

1)release() 是一个函数(未在此代码片段中展示,但通常是这样的),它的功能是将 rhs 中 pointee 指向的原生指针取出,并把 rhs 中的 pointee 设为 nullptr。这表示 rhs 放弃了对其管理的对象的所有权
pointee 将指向 rhs 之前所管理的对象

通过这种方式,所有权从 rhs 转移到当前的 auto_ptr 对象中

std::auto_ptr<int> p1(new int(5));
std::auto_ptr<int> p2(p1); // p2 接管了 p1 的所有权

2)rhs.release() 调用后 会释放 rhs 的所有权,将 rhs 管理的原生指针返回,并把 rhs.pointee 设为 nullptr,从而将 rhs 原本管理的对象 转移到当前 auto_ptr 对象中
reset() 接收这个指针并让当前 auto_ptr 对象管理它

当使用 auto_ptr 调用 -> 时,例如 p->myMethod(),这个调用 会先通过重载的 -> 操作符得到 pointee 指针,然后再解引用 该指针去调用 myMethod()。这使得 auto_ptr 对象 能够像普通指针一样使用 -> 访问 其所管理对象的成员

2、为 list(链表)设计一个迭代器。假设 list 及其节点的结构如下:

template <typename T>
class List
{
    
    
	void insert_front(T value);
	void insert_end(T value);
	void display(std::ostream &os = std::cout) const;
	// ...
private:
	ListItem<T>* _end;
	ListItem<T>* _front;
	long _size;
};

template <typename T>
class ListItem
{
    
    
public:
	T value() const {
    
     return _value; }
	ListItem* next() const {
    
     return _next; }
	...
private:
	T _value;
	ListItem* _next; // 单向链表
};

将这个 List 套用到 先前所说的 find() :
需要为它 设计一个行为类似指针的外衣,也就是 一个迭代器。当 解引用迭代器时,传回的应该是个 ListItem 对象;当递增该迭代器时,它应该 指向下一个 ListItem 对象。为了 让该迭代器适用于任何型态的节点,而不只限于 ListItem,可以将它设计为一个 类模板:

template <class Item> // Item 可以是单向链表节点或双向链表节点。
struct ListIter // 此处这个迭代器特定只为链表服务,因为其独特的 operator++ 
{
    
     
	Item* ptr; // 保持与容器之间的一个联系
	ListIter(Item* p = 0) // 默认构造函数
	: ptr(p) {
    
    }
	// 不必实现拷贝构造函数,因为编译器提供的缺省行为已足够
	// 不必实现 operator=,因为编译器提供的缺省行为已足够

	Item& operator* () const {
    
     return *ptr; }
	Item operator->() const {
    
     return ptr; }

	// 以下两个 operator++ 遵循标准做法
	// (1)前置递增运算符
	ListIter& operator++ ()
		{
    
     ptr = ptr->next(); return *this; }

	// (2)后置递增运算符
	ListIter operator++(int)
		{
    
     ListIter tmp = *this; ++*this; return tmp; }

	bool operator==(const ListIter& i) const
		{
    
     return ptr == i.ptr; }
	bool operator!=(const ListIter& i) const
		{
    
     return ptr != i.ptr; }
};

编译器会为类自动生成 一些默认的特殊成员函数,比如 拷贝构造函数、赋值运算符 operator= 和 默认构造函数等。具体来说:

  1. 拷贝构造函数 和 operator= 的缺省行为:
    拷贝构造函数:编译器生成的缺省拷贝构造函数 会进行成员逐一复制。对于 ListIter 类来说,只有一个数据成员 ptr(一个指针)。编译器 自动生成的拷贝构造函数 会将另一个 ListIter 对象的 ptr 复制到当前对象的 ptr。这对于 ListIter 来说是安全且足够的行为
    赋值运算符 operator=:同样地,编译器生成的缺省赋值运算符 也会逐一赋值,将一个 ListIter 对象的 ptr 值赋给另一个 ListIter 对象。因为 ptr 是指针类型,编译器的默认行为(直接复制指针值)也是合理的

对于 ListIter 这样只有一个指针成员的类,拷贝构造函数和赋值运算符的缺省行为 完全满足需求,所以不需要手动实现

  1. 默认构造函数需要自定义:
    默认构造函数:默认构造函数 在没有参数时创建对象。在 ListIter 中,自定义默认构造函数 ListIter(Item* p = 0) 是为了让 ptr 初始化为一个用户指定的 Item*,默认情况下是 0(即 nullptr)
    编译器生成的缺省默认构造函数 不会初始化 ptr。它只会 默认不做任何操作,导致 ptr 可能是未定义的值(即悬空指针)。为了避免这种情况,自定义默认构造函数 可以确保 ptr 在对象创建时 有一个确定的初始值(如 nullptr 或 用户传入的指针)

  2. 因为 前置递增运算符 是在递增之后 直接返回自身,因此 返回引用(ListIter&)可以减少不必要的拷贝,提供更高的效率

  3. 后置递增运算符 在使用对象原始值后再进行递增,因此 必须保存一份递增前的对象状态,然后 返回这份状态。为了实现这个过程,通常 会先创建一个原始对象的副本(tmp),再递增当前对象,最后返回这个副本

前置递增运算符 不需要复制对象,而 后置递增 需要额外创建副本,适合 那些需要保留递增前状态的情况

可以将 List 和 find() 藉由 ListIter 粘合起来:

List<int> mylist;
for(int i = 0; i < 5; ++i){
    
    
	mylist.insert_front(i); 
	mylist.insert_end(i + 2);
}
mylist.display(); // 10 ( 4 3 2 1 0 2 3 4 5 6 )

ListIter<ListItem<int>> begin(mylist.front()); 
ListIter<ListItem<int>> end; // default 0, null 
ListIter<ListItem<int>> iter; // default 0,null
iter = find(begin, end, 3);
if (iter == end)
	cout << "not found" << endl;
else
	cout << "found. " << iter->value() << endl;
// 执行结果: found. 3

iter = find(begin, end, 7);
if (iter == end)
	cout << "not found" << endl;
else
	cout << "found. " << iter->value() << endl;
// 执行结果: not found

由于 find() 函数内以 *iter != value 来检查元素值 是否吻合,而本例之中 value 的型别是 int,iter 的型别是 ListItem<int>,两者之间 并无可供使用的 operator!=,所以 必须另外写一个全局的 operator!= 重载函数,并以 int 和 ListItem<int> 作为 它的两个参数型别:

template <typename T>
bool operator!=(const ListItem<T>& item, T n)
{
    
     return item.value() != n; }

3、为了 完成一个针对 List 而设计的迭代器,不可避免地暴露了太多 List 实现细节:
在 main() 之中 为了制作 begin 和 end 两个迭代器,暴露了 ListItem; 在 ListIter class 之中 为了达成 operator++ 的目的,暴露了 ListItem 的操作函数 next()。如果 不是为了迭代器,ListItem 原本 应该完全隐藏起来不曝光的

要设计出 ListIter,首先必须对 List 的实现细节 有非常丰富的了解。既然这无可避免,干脆就把迭代器的开发工作 交给 List 的设计者,所有实现细节 反而得以封装起来 不被使用者看到。这正是为什么每一种 STL 容器都提供有 专属迭代器的缘故

3、迭代器相应类型

1、什么是相应类型?迭代器所指之物的类型 便是其中之一
C++ 只支持 sizeof(),并未支持 typeof()。即便动用 RTTI 性质中的 typeid(),获得的也只是 类型名称,不能拿来做变量声明

sizeof() 运算符,在编译时 返回类型或对象的大小,不会影响运行时性能

虽然 C++ 提供了 运行时类型识别(RTTI)功能,可以使用 typeid() 获取类型信息,但这只能得到类型的名称,无法用于变量声明

typeid() 运算符:
返回一个 std::type_info 对象,包含类型的信息
可以通过 .name() 方法获取类型的名称(通常是一个编译器特定的字符串)
限制:
返回的 type_info 对象不能用于声明变量,类型名称是一个字符串,无法在编译时解析为实际类型

class Base {
    
    };
class Derived : public Base {
    
    };

Base* b = new Derived();
std::cout << typeid(*b).name() << std::endl;  // 输出实际类型的名称

从 C++11 开始,引入了 auto 和 decltype(),用于 类型推导 和 获取表达式的类型

auto x = 10;          // x 的类型是 int
auto y = x + 5.5;     // y 的类型是 double

int a = 10;
decltype(a) b = 20;   // b 的类型与 a 相同,即 int
std::vector<int> vec;
decltype(vec.begin()) iter = vec.begin();  // iter 的类型与 vec.begin() 相同

解决办法是:利用 函数模板的参数推导 机制

template <class I, class T>
void func_impl(I iter, T t)
{
    
    
    T tmp; // 这里解决了问题。T 就是迭代器所指之物的型别,本例为 int

    // ... 这里做原本 func() 应该做的全部工作
};

template <class I>
inline
void func(I iter)
{
    
    
    func_impl(iter, *iter); // func 的工作全部移往 func_impl
}

int main()
{
    
    
    int i;
    func(&i);
}

以 func() 为对外接口,却把实际操作 全部置于 func_impl() 之中。由于 func_impl() 是一个 函数模板,一旦被调用,编译器会自动进行 模板 参数推导。于是导出类型 T

2、迭代器相应型别 不只是 “迭代器所指对象的类型” 一种而已。最常用的相应类型有五种,然而 并非任何情况下任何一种都可利用上述的 模板参数推导机制 来取得

4、Traits 编程技法:STL源代码门钥

1、迭代器所指对象的类型,称为 该迭代器的值类型 (value type)。上述的参数型别推导技巧 虽然可用于 value type,却非全面可用:万一 value type 必须用于函数的返回值,就束手无策了,毕竟函数的 “template 参数推导机制” 推导的 只是参数,无法推导函数的 返回值类型

2、声明 类的内嵌类型

template <class T>
struct MyIter {
    
    
    typedef T value_type;   // 内嵌类型声明
    T* ptr;
    MyIter(T* p=0) : ptr(p) {
    
     }
    T& operator*() const {
    
     return *ptr; }
    // ...
};

template <class I>
typename I::value_type  // 这一整行是 func 的返回值类型
func(I ite)
{
    
     return *ite; }

// ...
MyIter<int> ite(new int(8));
cout << func(ite);     // 输出: 8

func() 的返回类型 必须加上关键词 typename,因为 T 是一个 模板参数,在它被编译器具现化之前,编译器对 T 一无所悉,换句话说,编译器 此时并不知道 MyIter<T>::value_type 代表的是一个类型 或 是一个成员函数 或 是一个类成员。关键词 typename 的用意在于 告诉编译器这是一个类型

3、并不是所有迭代器都是 类类型。原生指针就不是。如果不是 类类型,就无法 为它定义内嵌型别。但 STL(以及整个泛型思维)绝对必须接受原生指针作为一种迭代器

原生指针(如 int*、double* 等)是 C++ 中最基本的指针类型,它们本身并不是类类型,也没有 成员函数或嵌套类型
原生指针 在语义上完全符合迭代器的行为,比如 它们可以进行解引用操作(*p),可以使用 递增和递减(++p 或 --p),以及 可以进行比较操作(p != q)

原生指针 作为迭代器使用时,由于 它是直接操作内存地址的,因此 具有最高的效率

为了 支持原生指针作为迭代器,STL 提供了类型萃取(type traits)机制,例如 std::iterator_traits,用于在模板中提取迭代器的类型信息

std::iterator_traits 的作用
定义:std::iterator_traits 是一个模板类,用于提取迭代器类型的相关信息

工作原理:iterator_traits 定义了五个标准的类型成员:
value_type:迭代器所指向的值的类型
difference_type:两个迭代器之间的距离类型
pointer:指向 value_type 的指针类型
reference:引用 value_type 的类型
iterator_category:迭代器的类别(如输入迭代器、输出迭代器、随机访问迭代器等)
支持原生指针:std::iterator_traits 对原生指针进行了特化,使得可以在模板中统一使用 iterator_traits 来获取这些类型信息,无论传入的迭代器是 类类型 还是 原生指针

#include <iostream>
#include <iterator>
#include <vector>
#include <type_traits>

template <typename Iterator>
void print_value_type(Iterator iter) {
    
    
    // 使用 iterator_traits 提取迭代器的 value_type
    typename std::iterator_traits<Iterator>::value_type value = *iter;
    std::cout << "Value type: " << value << std::endl;
}

int main() {
    
    
    std::vector<int> vec = {
    
    1, 2, 3};
    print_value_type(vec.begin());  // vec.begin() 是类类型迭代器

    int arr[] = {
    
    4, 5, 6};
    print_value_type(arr);          // arr 是原生指针类型,支持作为迭代器
    return 0;
}

原生指针作为 迭代器时,其实是 随机访问迭代器的一种。STL 中的算法 可以根据迭代器的 iterator_category 进行不同的优化,原生指针的 iterator_category 被特化为 std::random_access_iterator_tag,这使得它能够与 std::vector 等随机访问容器的迭代器一起使用,实现高效的算法操作

让上述的一般化概念 针对特定情况(例如针对原生指针)做特殊化处理:模板部分特化(template partial specialization)

4、Partial Specialization (偏特化) 的意义
如果 类型模板拥有一个以上的 模板参数,可以针对其中某个(或数个,但非全部)模板参数进行特化工作,可以在泛化设计中 提供一个特化版本(也就是 将泛化版本中的某些模板参数 赋予明确的指定)
不要求 完全指定所有模板参数的具体类型。这与完全特化不同,完全特化 是为所有模板参数的具体类型 提供特定的实现

部分特化常用于类模板中,C++ 并不支持函数模板的部分特化

// 通用模板
template <typename T, typename U>
class MyClass {
    
    
public:
    void print() {
    
    
        std::cout << "General template" << std::endl;
    }
};

// 部分特化:当第一个参数是 int 时的特化版本
template <typename U>
class MyClass<int, U> {
    
    
public:
    void print() {
    
    
        std::cout << "Partial specialization where T is int" << std::endl;
    }
};

// 部分特化:当两个类型相同时的特化版本
template <typename T>
class MyClass<T, T> {
    
    
public:
    void print() {
    
    
        std::cout << "Partial specialization where T and U are the same" << std::endl;
    }
};

MyClass<double, double> obj1;
obj1.print();  // 输出: Partial specialization where T and U are the same

MyClass<int, float> obj2;
obj2.print();  // 输出: Partial specialization where T is int

MyClass<float, int> obj3;
obj3.print();  // 输出: General template

假设 有一个类型模板如下:

template<typename T>
class C {
    
     ... }; // 这个泛化版本允许(接受)T 为任何型别

有一个形式如下的 partial specialization

template<typename T>
class C<T*> {
    
     ... }; // 这个特化版本仅适用于 "T 为原生指针"的情况
// "T 为原生指针"便是 "T 为任何型别"的一个更进一步的条件限制

可以解决 前述 “内嵌类型” 未能解决的问题,即 原生指针并非 class,因此无法 为它们定义内嵌类型。现在,可以针对 “迭代器之 模板参数为指针” 者,设计特化版的迭代器

下面这个类型模板 专门用来 “萃取” 迭代器的特性,而 value type 正是迭代器的特性之一:

template <class I>
struct iterator_traits {
    
     // traits 意为“特性”
    typedef typename I::value_type value_type;
};

traits 意义是,如果 I 定义有自己的 value type,那么通过这个 traits 的作用,萃取出来的 value_type 就是 I::value_type。换句话说,如果 I 定义有自己的 value type,先前那个 func() 可以改写成这样:

template <class I>
typename iterator_traits<I>::value_type  // 这一整行是函数返回型别
func(I ite)
{
    
     return *ite; }

除了 多一层间接性,又带来的好处是 traits 可以拥有特化版本。现在,令 iterator_traits 拥有一个 partial specializations 如下:

template <class T>
struct iterator_traits<T*> {
    
       // 偏特化版——迭代器是个原生指针
    typedef T value_type;
};

原生指针 int* 虽然 不是一种 class type, 亦可通过 traits 取其 value type。这就解决了先前的问题

5、针对 “指向常数对象的指针”

iterator_traits<const int*>::value_type

获得的是 const int 而非 int,但是 希望利用这种机制来声明一个暂时变量,使其类型 与迭代器的 value type 相同,而现在,声明一个无法赋值(因 const 之故)的暂时变量,没什么用
因此,如果迭代器是个 指向常数对象的指针, 应该设法令其 value type 为一个 non-const 型别。没问题,只要 另外设计一个特化版本,就能解决这个问题:

template <class T>
struct iterator_traits<const T*> {
    
     // 偏特化版——当迭代器是个 指向常数对象的指针 时,
    typedef T value_type;          // 萃取出来的型别应该是 T 而非 const T
};

不论面对的是迭代器 MyIter, 或是 原生指针 int* 或 const int*, 都可以通过 traits 取出正确的 value type

traits 所扮演的 “特性萃取机” 角色,萃取 各个迭代器的特性。这里所谓的迭代器特性,指的是 迭代器的相应类型(associated types)
若要这个 “特性萃取机” traits 能够有效运作,每一个迭代器 必须遵循约定,自行 以内嵌型别定义的方式 定义出相应型别(associated types)。谁不遵守这个约定,谁就不能兼容于 STL
在这里插入图片描述
6、最常用到的迭代器相应型别有五种:value type, difference type, pointer, reference, iterator category。如果 希望所开发的容器能与 STL 适配,一定要为 你的容器的迭代器 定义这五种相应型别。“特性萃取机” traits 会将 迭代器的相应类型 榨取出来:

template <class I>
struct iterator_traits {
    
    
    typedef typename I::iterator_category   iterator_category;
    typedef typename I::value_type          value_type;
    typedef typename I::difference_type     difference_type;
    typedef typename I::pointer             pointer;
    typedef typename I::reference           reference;
};

iterator_traits 必须针对传入之型别为 指针 及 常量指针 者,设计特化版本

4.1 迭代器相应型别之一: value type

value type,是指 迭代器所指对象的类型。任何一个打算与 STL 算法有完美搭配的 类(迭代器),都应该定义自己的 value type 内嵌型别,做法就像上节所述

4.2 迭代器相应型别之二: difference type

difference type 用来表示 两个迭代器之间的距离,因此 它也可以用来表示 一个容器的最大容量,因为 对于连续空间的容器而言,头尾之间的距离 就是其最大容量。如果一个泛型算法 提供计数功能,例如 STL 的 count(),其返回值 就必须使用迭代器的 difference type:

template <class I, class T>
typename iterator_traits<I>::difference_type // 这一整行是函数返回类型
count(I first, I last, const T& value) {
    
    
    typename iterator_traits<I>::difference_type n = 0;
    for (; first != last; ++first)
        if (*first == value)
            ++n;
    return n;
}

针对相应型别 difference type,traits 的如下两个(针对原生指针而写的)特化版本,以 C++ 内建的 ptrdiff_t(定义于 <cstddef> 头文件,有符号整数类型(区别于 size_t),表示 两个指针之间的差值)
在这里插入图片描述
size_t 和 ptrdiff_t 可以转换,但需要注意类型是否匹配,因为 size_t 是无符号类型,转换成有符号类型可能会导致数据截断或溢出

作为原生指针的 difference type:

template <class I>
struct iterator_traits {
    
    
    ...
    typedef typename I::difference_type difference_type;
};

// 针对原生指针而设计的 “偏特化(partial specialization)” 版
template <class T>
struct iterator_traits<T*> {
    
    
    ...
    typedef ptrdiff_t difference_type;
};

// 针对原生的 pointer-to-const 而设计的 “偏特化(partial specialization)” 版
template <class T>
struct iterator_traits<const T*> {
    
    
    ...
    typedef ptrdiff_t difference_type;
};

任何时候 当 需要任何迭代器 I 的 difference type ,可以这么写:

typename iterator_traits<I>::difference_type

4.3 迭代器相应型别之三: reference type

1、从 “迭代器所指之物的内容 是否允许改变” 的角度观之,迭代器分为两种:
不允许改变 “所指对象之内容” 者,称为 常量迭代器, 例如 const int* pic;
允许改变 “所指对象之内容” 者,称为 可变迭代器,例如 int* pi。当对一个可变迭代器 进行解引用操作时,获得的 不应该是一个右值(rvalue),应该是一个左值(lvalue),因为右值不允许赋值操作,左值才允许:

int* pi = new int(5);
const int* pci = new int(9);
*pi = 7; // 对可变迭代器进行解引用操作时,获得的是个左值,允许赋值
*pci = 1; // 这个操作不允许,因为pci是个常量迭代器,解引用pci所得结果,是个右值,不允许被赋值

函数 如果要传回左值,都是以 引用 的方式进行,所以当 p 是个可变迭代器时,如果其 value type 是 T,那么 p 的型别不应该是T,应该是 T&。将此道理扩充,如果 p 是个 常量迭代器,其 value type 是 T,那么 p 的型别不应该是 const T,而应该是 const T&

4.4 迭代器相应型别之四:pointer type

Item& operator*( ) const {
    
     return *ptr; }
Item* operator->() const {
    
     return ptr; }

Item& 便是 ListIter 的 reference type,而 Item* 便是其 pointer type

把 reference type 和 pointer type 这两个相应型别加入 traits 内:

template <class I>
struct iterator_traits {
    
    
	...
	typedef typename I::pointer pointer;
	typedef typename I::reference reference;
};

// 针对原生指针而设计的 “偏特化版 (partial specialization)”
template <class T>
struct iterator_traits<T*> {
    
    
	...
	typedef T* pointer;
	typedef T& reference;
};

// 针对原生的指向常量的指针 而设计的 “偏特化版(partial specialization)”
template <class T>
struct iterator_traits<const T*> {
    
    
	...
	typedef const T* pointer;
	typedef const T& reference;
};

4.5 迭代器相应型别之五: iterator_category

1、迭代器 通常被细分为五类,每一类 有其特定的用途和特性。它们的分类 基于迭代器 在数据结构中移动的方式 以及可以执行的操作

  • 输入迭代器(Input Iterator):只能 从数据流中读取元素,适合于 一次性遍历的数据源,例如 读取文件或输入流。它们支持只读访问 并且只能前向迭代
  • 输出迭代器(Output Iterator):用于 向数据流中写入元素,适合于 一次性写入的数据源,例如 输出流或赋值操作。它们支持写入操作 并且也是只能前向迭代
  • 前向迭代器(Forward Iterator):支持 只读和写操作,可以多次遍历同一个范围。前向迭代器 可以通过递增操作向前移动,但不支持向后移动
  • 双向迭代器(Bidirectional Iterator):可以 前后双向移动的迭代器,适用于 双向链表等数据结构。它们 不仅支持向前移动(++操作),也支持 向后移动(–操作)
  • 随机访问迭代器(Random Access Iterator):可以 在常数时间内 通过偏移量进行任意位置的访问,类似于 指针,适用于数组、向量等支持随机访问的数据结构。它们支持算术运算(如 + 和 -),因此可以 灵活地访问任意位置的元素

前四种迭代器 都只供应 一部分指针算术能力 (前三种支持 operator++,第四种再加上 operator–),第五种 则涵盖所有指针算术能力,包括 p+n, p-n, p[n], pl-p2, pl<p2
在这里插入图片描述
(箭头表示 概念 与 强化 关系)
在STL(标准模板库)中,“概念” 与 “强化” 是其设计和架构的重要观念

1)概念在 STL 中是用来 定义一类类型 或 对象所需满足的约束或要求。它类似于 接口或协议的思想,用于描述 数据类型或算法需要具备的性质和行为。概念 可以理解为一种抽象的规范,用于约束模板参数

在 STL 中,概念用于 定义迭代器、容器和算法之间的协同关系。STL算法 不会直接依赖具体的数据类型,而是依赖于 迭代器或容器需要满足的行为(如随机访问、前向遍历等)。这使得 STL 中的组件之间 可以通过满足特定概念 来实现互操作和复用

通过使用概念,STL 实现了 模板的泛型编程,可以对不同的数据类型进行抽象,允许 同一个算法在多种容器上运行

2)强化 是对概念的扩展或细化,它描述了 一个概念是如何在 另一个更为具体的概念上进行细化的。通过强化,STL 可以逐步增加数据类型 或 迭代器的功能,以满足 特定场景下更高的要求

随机访问迭代器 是 双向迭代器 的强化,因为 它不仅能够前后遍历,还能够 在常数时间内进行随机位置访问
双向迭代器 是 前向迭代器 的强化,因为它不仅可以向前遍历,还增加了向后遍历的能力

2、尽量针对 图3-2 中的某种迭代器 提供一个明确定义,并针对 更强化的某种迭代器 提供另一种定义,这样才能在不同情况下 提供最大效率
假设有个算法可接受 Forward Iterator,你以 Random Access Iterator 喂给它,它当然也会接受,因为一个 Random Access Iterator 必然是一个 Forward Iterator,但是可用并不代表最佳

拿 advance() 来说(这是许多算法内部 常用的一个函数),该函数 有两个参数:迭代器 p 和数值 n;函数内部将 p 累进 n 次(前进 n 距离)。下面有三份定义,一份针对 Input Iterator,一份针对 Bidirectional Iterator,另一份针对 Random Access Iterator。没有针对 Forward Iterator 而设计的版本,因为那和针对 Input Iterator 而设计的版本完全一致

template <class InputIterator, class Distance>
void advance_II(InputIterator& i, Distance n)
{
    
    
    // 单向,逐一前进
    while (n--) ++i;
    // 或写 for (; n > 0; --n, ++i);
}

template <class BidirectionalIterator, class Distance>
void advance_BI(BidirectionalIterator& i, Distance n)
{
    
    
    // 双向,逐一前进
    if (n >= 0)
        while (n--) ++i;       // 或写 for (; n > 0; --n, ++i);
    else
        while (n++) --i;       // 或写 for (; n < 0; ++n, --i);
}

template <class RandomAccessIterator, class Distance>
void advance_RAI(RandomAccessIterator& i, Distance n)
{
    
    
    // 双向,跳跃前进
    i += n;
}

当程序调用 advance() 时,应该选用(调用)哪一份函数定义
如果选择 advance_II(),对 Random Access Iterator 而言极度缺乏效率,原本 O(1) 的操作竟成为 O(N)。如果选择 advance_RAI(),则它无法接受 Input Iterator。需要将三者合一:

template <class InputIterator, class Distance>
void advance(InputIterator& i, Distance n)
{
    
    
    if (is_random_access_iterator(i))     // 此函数有待设计
        advance_RAI(i, n);
    else if (is_bidirectional_iterator(i))   // 此函数有待设计
        advance_BI(i, n);
    else
        advance_II(i, n);
}

执行时期 才决定使用哪一个版本,会影响 程序效率。最好能够在编译期 就选择正确的版本。重载函数机制 可以达成这个目标

如果 traits 有能力萃取出迭代器的种类,便可利用这个 “迭代器类型” 相应类别作为 advanced() 的第三参数。这个相应类别一定必须是一个 类类型,不能只是 数值号码类的东西,因为 编译器需仰赖它(一个类别)来进行重载决议(当一个函数名对应多个函数实现(即函数重载)时,编译器 需要根据调用时 提供的参数来决定具体调用哪个函数实现)

下面定义五个类,代表五种迭代器类型:

// 五个作为标记用的类别(tag types)
struct input_iterator_tag {
    
     };
struct output_iterator_tag {
    
     };
struct forward_iterator_tag : public input_iterator_tag {
    
     };
struct bidirectional_iterator_tag : public forward_iterator_tag {
    
     };
struct random_access_iterator_tag : public bidirectional_iterator_tag {
    
     };

设计这种 继承关系 有以下2个主要原因:
1)可以清楚地表达 这些迭代器类型之间的 强化 关系,即更高级别的迭代器 具有低级别迭代器的所有特性,并在此基础上 扩展出更多能力

2)继承机制的另一个重要作用是 在编译期通过标签进行算法的选择,即根据迭代器的类型 选择合适的算法版本
假设有一个 distance 函数用于计算两个迭代器之间的距离:

template <typename Iterator>
typename iterator_traits<Iterator>::difference_type
distance(Iterator first, Iterator last, input_iterator_tag) {
    
    
    // 针对输入迭代器的距离计算,逐步遍历
}

template <typename Iterator>
typename iterator_traits<Iterator>::difference_type
distance(Iterator first, Iterator last, random_access_iterator_tag) {
    
    
    // 针对随机访问迭代器的距离计算,使用直接减法
    return last - first;
}

根据传入的迭代器的类型,编译器 会选择最合适的 distance 函数版本:
如果传入的是一个 random_access_iterator_tag,编译器 会选择第二个版本,因为 它可以直接使用减法进行计算
如果传入的是一个 input_iterator_tag,编译器 会选择第一个版本,因为 只能通过逐步遍历来计算距离

由于 random_access_iterator_tag 是从 input_iterator_tag 派生而来的
继承关系 使得在选择算法时可以匹配到最具体的版本,同时 也支持向更基础的版本进行回退

这些类 只作为标记用,所以不需要任何成员(设计目标是 通过类型本身的存在 来进行区分,而不涉及 任何数据存储或行为实现)
在模板元编程中 传递类型信息。例如,在编译期,可以通过传递这些标记类型 来选择不同的算法版本或实现方式,标记类型被用作第三个参数,以便在编译期 根据迭代器类型选择相应的函数

3、重新设计_advance()(由于只在内部使用,所以函数名称 加上特定的前导符),并加上第三参数,使它们形成重载:

template <class InputIterator, class Distance>
inline void _advance(InputIterator& i, Distance n,
                     input_iterator_tag)
{
    
    
    // 单向,逐一前进
    while (n--) ++i;
}

// 这是一个单纯的传递调用函数(trivial forwarding function)。稍后讨论如何免除之
template <class ForwardIterator, class Distance>
inline void _advance(ForwardIterator& i, Distance n,
                     forward_iterator_tag)
{
    
    
    // 单纯地进行传递调用(forwarding)
    advance(i, n, input_iterator_tag());
}

_advance 函数对 forward_iterator_tag 进行处理时,调用的是 advance 而不是 _advance,这涉及到模板设计中 “接口函数与实现细节的分离” 的原则
希望 用户调用 advance 函数,而不需要了解 _advance 的具体实现逻辑。通过 将实现细节封装在 _advance 中,可以简化用户的使用,也更容易进行维护
advance 是一个通用接口,它会根据传入的迭代器类型,选择合适的 _advance 实现

template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n,
                      bidirectional_iterator_tag)
{
    
    
    // 双向,逐一前进
    if (n >= 0)
        while (n--) ++i;
    else
        while (n++) --i;
}

template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n,
                      random_access_iterator_tag)
{
    
    
    // 双向,跳跃前进
    i += n;
}

每个 __advance() 的最后一个参数都 只声明型别,并未 指定参数名称,因为它纯粹只是 用来激活重载机制,函数之中 根本不使用该参数

4、还需要 一个对外开放的上层控制接口,调用 上述各个重载的 __advance()。这一上层接口 只需两个参数,当它准备将工作转给上述的 __advance() 时,才自行加上第三参数:迭代器类型。因此,这个上层函数 必须有能力从它所获得的迭代器中 推导出其类型——这份工作 自然是交给 traits 机制:

template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n)
{
    
    
    __advance(i, n,
              iterator_traits<InputIterator>::iterator_category());
}

iterator_traits<Iterator>::iterator_category() 将产生一个暂时对象,其型别 隶属于前述五个迭代器类型之一。然后,根据这个型别,编译器 才决定调用哪一个 __advance() 重载函数

为了满足上述行为,traits 必须再增加一个相应的类别:

template <class I>
struct iterator_traits {
    
    
	...
	typedef typename I::iterator_category   iterator_category;
};

// 针对原生指针而设计的“偏特化版 (partial specialization)”
template <class T>
struct iterator_traits<T*> {
    
    
	...
	// 注意,原生指针是一种 Random Access Iterator
	typedef random_access_iterator_tag   iterator_category;
};

// 针对原生的 pointer-to-const 而设计的 “偏特化版 (partial specialization)”
template <class T>
struct iterator_traits<const T*> {
    
    
	...
	// 注意,原生的 指向常量的指针 是一种 Random Access Iterator
	typedef random_access_iterator_tag   iterator_category;
};

任何一个迭代器,其类型 永远是 “该迭代器所隶属的各种类型中,最强化的那个”。例如,int* 既是 Random Access Iterator,又是 Bidirectional Iterator,同时也是 Forward Iterator,而且也是 Input Iterator,那么,其类型应该归属为random_access_iterator_tag(跟选择调用函数时 相反)

advance() 的 template 参数名称 取得 不怎么理想:

template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n);

按说 advanced() 既然可以 接受各种类型的迭代器,就不应 将其型别参数命名为 InputIterator。这其实是 STL 算法的一个命名规则:以算法所能接受的 最低阶迭代器类型,来为其迭代器型别参数命名(只是名字)

5、消除 “单纯传递调用的函数”
以类来定义 迭代器的各种分类标签,不仅可以 促成重载机制的成功运作(使编译器 得以正确执行重载决议),另一个好处是,通过继承,可以不必 再写 “单纯只做传递调用” 的函数

在这里插入图片描述

#include <iostream>
using namespace std;

struct B {
    
     }; // B 可比拟为 InputIterator
struct D1 : public B {
    
     }; // D1 可比拟为 ForwardIterator
struct D2 : public D1 {
    
     }; // D2 可比拟为 BidirectionalIterator

template <class I>
func(I& p, B)
{
    
     cout << "B version" << endl; }

template <class I>
func(I& p, D2)
{
    
     cout << "D2 version" << endl; }

int main()
{
    
    
    int* p;
    func(p, B()); // 参数与参数完全吻合。输出: "B version"
    func(p, D1()); // 参数与参数未能完全吻合;因继承关系而自动传递调用
                   // 输出:"B version"
    func(p, D2()); // 参数与参数完全吻合。输出: "D2 version"
}

6、当模板函数 参与函数匹配时,编译器 首先尝试找到一个完全匹配的非模板函数(如果有)。如果 没有完全匹配的非模板函数,它会寻找模板函数 并进行实例化

template <typename T>
void bar(T t) {
    
    
    std::cout << "Template version\n";
}

void bar(Base b) {
    
    
    std::cout << "Non-template Base version\n";
}

调用 bar 函数:

Derived d;
bar(d); // 输出 "Template version"

改为调用 bar 时传入一个 Base 对象(完全匹配):

Base b;
bar(b); // 输出 "Non-template Base version"

特化 并不仅限于在有继承关系的情况下使用。它是 C++ 模板编程中广泛使用的一种技术,可以在 各种类型的匹配和优化中应用
1)全特化
全特化 是针对一个模板进行完全具体化的类型替换,即为 某一个特定类型提供一个专门的实现。在全特化中,不需要继承关系,只需要 为某个特定类型编写一个特化版本

template <typename T>
void func(T t) {
    
    
    std::cout << "Generic version\n";
}

// 针对 int 类型的全特化
template <>
void func<int>(int t) {
    
    
    std::cout << "Specialized for int\n";
}

func(10); // 输出 "Specialized for int"
func(3.14); // 输出 "Generic version"

2)偏特化
偏特化是 对模板进行部分的类型特化,它允许 为某些模板参数的特定类型 或 类型组合 编写更具体的实现。偏特化 通常用于模板类 而不是模板函数,因为 C++ 不支持对函数模板进行部分特化

// 偏特化:当第一个类型是 int 时
template <typename U>
struct Pair<int, U> {
    
    
    void print() {
    
    
        std::cout << "Specialized Pair where first type is int\n";
    }
};

// 偏特化:当第二个类型是 int 时
template <typename T>
struct Pair<T, int> {
    
    
    void print() {
    
    
        std::cout << "Specialized Pair where second type is int\n";
    }
};

一、继承与特化的关系
虽然特化 不依赖于继承关系,但继承 可以与特化结合使用,以更灵活地设计类和函数

二、特化与非继承的场景
为不同数据类型 提供优化:例如为 char 类型和 double 类型的处理方式 做不同的优化,而这些类型之间并没有任何继承关系
处理指针类型:例如为 指针类型(T*)和普通类型(T)提供不同的特化实现
处理数组类型:例如为 数组类型(T[])提供特定的处理逻辑

template <typename T>
struct TypeInfo {
    
    
    static void print() {
    
    
        std::cout << "General type\n";
    }
};

// 针对指针类型的特化
template <typename T>
struct TypeInfo<T*> {
    
    
    static void print() {
    
    
        std::cout << "Pointer type\n";
    }
};

TypeInfo<int>::print(); // 输出 "General type"
TypeInfo<int*>::print(); // 输出 "Pointer type"

SFINAE 是 Substitution Failure Is Not An Error 的缩写,它是 C++ 模板编程中的一个概念,意思是 “替换失败不算错误”。它允许 在模板参数替换过程中,如果 某个模板参数的替换 导致编译时错误,编译器 不会立即报错,而是 会排除该模板版本,并继续尝试 其他匹配的模板。这使得 可以在模板中 通过某些条件来控制模板的选择,从而实现更灵活的模板编程

SFINAE 通常与 std::enable_if、类型特征(如 std::is_integral、std::is_floating_point)等结合使用。以下是一些常见的例子:
1)基于 std::enable_if 的例子
std::enable_if 是标准库提供的一个工具类,用于 在条件满足时启用特定的模板实例化。例如:

#include <iostream>
#include <type_traits>

// 如果 T 是整数类型,启用这个模板版本
template <typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
print(T t) {
    
    
    std::cout << "Integral type: " << t << std::endl;
}

// 如果 T 不是整数类型,启用这个模板版本
template <typename T>
typename std::enable_if<!std::is_integral<T>::value, void>::type
print(T t) {
    
    
    std::cout << "Non-integral type: " << t << std::endl;
}

int main() {
    
    
    int i = 42;
    double d = 3.14;

    print(i);  // 输出 "Integral type: 42"
    print(d);  // 输出 "Non-integral type: 3.14"

    return 0;
}

当 print 的模板参数是 一个整数类型时,std::is_integral<T>::value 为 true,启用第一个版本

2)has_size 类模板使用 SFINAE 来检查类型 T 是否有 size() 成员函数
第一个 test 函数用于检查 T 是否有 size(),如果有,则返回 std::true_type
第二个 test 是一个“兜底”的版本,用于匹配所有其他情况(即如果 T 没有 size()),它返回 std::false_type

#include <iostream>
#include <type_traits> // <type_traits> 提供了类型特征工具(如 std::true_type 和 std::false_type)
#include <vector>

// 检查 T 是否具有成员函数 size()
template <typename T>
class has_size {
    
    
private:
    template <typename U> // 用于检查类型 U 是否有 size() 成员函数
    static auto test(int) -> decltype(std::declval<U>().size(), std::true_type());
    // std::declval<U>() 是一个工具,可以获得 U 类型的右值引用(U&&),模拟出一个类型为 U 的对象,而无需实际构造它
	// std::declval<U>().size() 表达式模拟调用 size() 成员函数,如果 U 有 size() 成员函数,表达式可以被正确解析
	// 逗号运算符, std::true_type() 用于使 decltype 的结果为 std::true_type,表明类型 U 确实有 size() 成员函数
	// test(int) 的返回类型是 std::true_type,如果类型 U 有 size() 成员函数,那么这个函数就能被实例化
	
    template <typename>
    static std::false_type test(...);
    // 这是一个接受变参的模板函数,用于匹配所有类型
	// ... 表示任意类型和任意数量的参数,这个函数的返回类型是 std::false_type

public:
    static constexpr bool value = decltype(test<T>(0))::value;
    // 这个 value 是 has_size 的公有静态成员,constexpr 表明它在编译时就可以确定值
	// 它的值是通过调用 test<T>(0) 得到的返回类型的 ::value
	// test<T>(0) 会首先尝试匹配第一个版本,即 test(int),如果 T 有 size() 成员函数,返回 std::true_type,即 true
	// 如果第一个版本不可用(替换失败),编译器会选择第二个版本 test(...),返回 std::false_type,即 false
};

// 测试用例
int main() {
    
    
    std::cout << has_size<std::vector<int>>::value << std::endl;  // 输出 1 (true)
    std::cout << has_size<int>::value << std::endl;               // 输出 0 (false)
    return 0;
}

decltype 可以直接返回表达式的类型
与 auto 的区别: decltype 仅获取表达式的类型,而不进行类型推导

int a = 5;
const int& b = a;

auto x = b;         // x 的类型是 int,auto 去掉了引用和 const
decltype(b) y = b;  // y 的类型是 const int&,保持了 b 的类型

获取函数返回类型: decltype 通常和 std::declval 结合使用,以便在不实际调用函数的情况下 推导其返回类型

template <typename T>
auto getSize(const T& container) -> decltype(container.size()) {
    
    
    return container.size();
}
// decltype(container.size()) 的作用是获取 container.size() 的返回类型,并用它作为 getSize 的返回类型

std::true_type 是一个类型,它包含一个 constexpr 的静态成员 value,其值为 true

decltype 本身 并没有直接接受两个参数的情况,逗号运算符(,)是一个二元运算符,它会对两个表达式求值,并返回第二个表达式的值

decltype(expression1, expression2)

逗号表达式会先对 expression1 求值,再对 expression2 求值,最终 decltype 的结果会是 expression2 的类型,而不关心 expression1 的结果

decltype(std::declval<T>().size(), std::true_type{}):

逗号表达式 std::declval<T>().size(), std::true_type{} 会首先对 std::declval<T>().size() 求值
如果 T 类型有 size() 成员函数,std::declval<T>().size() 是合法的,因此不会触发编译错误
然后,逗号表达式的最终结果类型是 std::true_type,即 decltype 的结果是 std::true_type

如果 T 没有 size() 成员函数:
std::declval<T>().size() 这一部分会导致编译错误
由于使用了 SFINAE,编译器会认为这个特化版本不可用,而去匹配其他的 test(…) 函数
test(…) 是一个接受任意参数的函数,它返回 std::false_type

7、以 distance() 为例,整个设计和 advance() 一样

template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
_distance(InputIterator first, InputIterator last,
          input_iterator_tag) {
    
    
  iterator_traits<InputIterator>::difference_type n = 0;
  // 逐一累计距离
  while (first != last) {
    
    
    ++first; ++n;
  }
  return n;
}

template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
_distance(RandomAccessIterator first, RandomAccessIterator last,
          random_access_iterator_tag) {
    
    
  // 直接计算差距
  return last - first;
}

// distance() 可接受任何类型的迭代器;其 template 型别参数之所以命名为 InputIterator
// 是为了遵循 STL 算法的命名规则:以算法所能接受之最初级类型来为其迭代器型别参数命名
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last) {
    
    
  typedef typename iterator_traits<InputIterator>::iterator_category category;
  return _distance(first, last, category());
}

由于 迭代器类型之间 存在着继承关系,“传递调用” 的行为模式 因此自然存在
当客户端调用 distance() 并使用 Output Iterators 或 Forward Iterators 或 Bidirectional Iterators 时,统统都会传递调用 Input Iterator 版的那个 _distance() 函数

5、std::iterator 的保证

1、任何迭代器 都应该提供 五个内嵌相应型别,以利于 traits 萃取,否则 便是自别于整个 STL 架构,可能 无法与其它 STL 组件顺利搭配。将事情简化,STL 提供了一个 iterators class 如下,如果每个新设计的迭代器 都继承自它,就可保证符合 STL 所需之规范:

template <class Category,
          class T,
          class Distance = ptrdiff_t,
          class Pointer = T*,
          class Reference = T&>

struct iterator {
    
    
  typedef Category     iterator_category;
  typedef T            value_type;
  typedef Distance     difference_type;
  typedef Pointer      pointer;
  typedef Reference    reference;
};

ptrdiff_t 常用于迭代器,因为迭代器的操作 需要计算位置差距,而 ptrdiff_t 的大小 可以适应不同平台上的指针距离
当没有为模板参数 Distance、Pointer 和 Reference 指定类型时,分别采用 ptrdiff_t、T* 和 T& 作为默认类型

iterator class 不含任何成员,纯粹 只是型别定义,所以继承它 并不会招致任何额外负担。由于 后三个参数皆有默认值,故新的迭代器 只需提供前两个参数即可
先前 3.2 节的 ListIter,如果改用正式规格,应该这么写:

template <class Item>
struct ListIter :
   public std::iterator<std::forward_iterator_tag, Item> {
    
     ... }

2、设计适当的 相应类型(associated types),是迭代器的责任。设计适当的迭代器,则是容器的责任。唯容器本身,才知道该设计出怎样的迭代器 来遍历自己,并执行 迭代器应有的各种行为(前进、后退、取值、取用成员…)。至于算法,完全可以独立于容器和迭代器之外 自行发展,只要设计时 以迭代器为对外接口就行

traits 编程技法 大量运用于 STL 实现品中。它利用 “内嵌型别” 的编程技巧 与 编译器的 template 参数推导功能,增强 C++ 未能提供的 关于型别认证方面的能力,弥补 C++ 不为强型别语言的遗憾

3、除了上述的办法外,还可以 使用 type traits 和 SFINAE 进行类型认证
假设 定义一组 Shape 类,并在其中 定义一个内嵌类型 is_shape 作为标志,表示该类型是 一个形状类型。然后在模板函数中,可以检查类型是否拥有 此内嵌类型,从而实现认证

#include <iostream>
#include <type_traits>

// 定义一个基础类 Shape,包含一个 is_shape 的内嵌类型,定义为 std::true_type
struct Shape {
    
    
    using is_shape = std::true_type;
    // 表示任何继承 Shape 的类都可以通过此标志来表示它是一个“形状类型”
    // Circle 和 Square 是 Shape 的派生类,因此它们也会继承 Shape 中的 is_shape 内嵌类型
	// NotShape 则不是 Shape 的派生类,不包含 is_shape 内嵌类型
};

struct Circle : public Shape {
    
    };
struct Square : public Shape {
    
    };

struct NotShape {
    
    };

// 定义一个判断是否为 Shape 类型的辅助模板
// is_shape_type 是一个类型萃取辅助模板结构体,用来判断一个类型 T 是否具有 is_shape 内嵌类型

// 通用模板(未特化)
// 定义了一个默认的情况:假设 T 没有 is_shape 内嵌类型时,is_shape_type<T> 为 std::false_type,表示 false
template <typename T, typename = void>
struct is_shape_type : std::false_type {
    
    };

template <typename T>
struct is_shape_type<T, std::void_t<typename T::is_shape>> : std::true_type {
    
    };

// 定义一个函数,只有当类型是 Shape 时才能调用
template <typename T>
typename std::enable_if<is_shape_type<T>::value>::type
draw(const T&) {
    
    
    std::cout << "Drawing shape!" << std::endl;
}

int main() {
    
    
    Circle c;
    Square s;
    NotShape n;

    draw(c); // OK: Circle 是 Shape 的子类
    draw(s); // OK: Square 是 Shape 的子类
    // draw(n); // Error: NotShape 不是 Shape 的子类,无法编译
}

template <typename T>
struct is_shape_type<T, std::void_t<typename T::is_shape>> : std::true_type {
    
    };

这个特化版本通过 std::void_t<typename T::is_shape> 实现了一种条件匹配:如果 T 中存在内嵌类型 is_shape,则 std::void_t<typename T::is_shape> 为有效类型,匹配此特化版本,此时 is_shape_type<T> 继承 std::true_type,表示 true

std::void_t 是一种用于 条件检测的工具类型,它接受 一组类型作为参数,并返回 void。若 T 拥有内嵌类型 is_shape,则 std::void_t<typename T::is_shape> 是有效的;若 T 没有 is_shape,则匹配失败,编译器会使用默认的 is_shape_type 定义,即 false_type

6、iterator 源代码完整重列

// 五种迭代器类型
struct input_iterator_tag {
    
    };
struct output_iterator_tag {
    
    };
struct forward_iterator_tag : public input_iterator_tag {
    
    };
struct bidirectional_iterator_tag : public forward_iterator_tag {
    
    };
struct random_access_iterator_tag : public bidirectional_iterator_tag {
    
    };

// 为避免写代码时挂一漏万, 自行开发的迭代器最好继承自下面这个 std::iterator
template <class Category, class T, class Distance = ptrdiff_t,
          class Pointer = T*, class Reference = T&>
struct iterator {
    
    
    typedef Category iterator_category;
    typedef T value_type;
    typedef Distance difference_type;
    typedef Pointer pointer;
    typedef Reference reference;
};

// "榨汁机"traits
template <class Iterator>
struct iterator_traits {
    
    
    typedef typename Iterator::iterator_category iterator_category;
    typedef typename Iterator::value_type value_type;
    typedef typename Iterator::difference_type difference_type;
    typedef typename Iterator::pointer pointer;
    typedef typename Iterator::reference reference;
};

// 针对原生指针 (native pointer) 而设计的 traits 偏特化版
template <class T>
struct iterator_traits<T*> {
    
    
    typedef random_access_iterator_tag iterator_category;
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef T* pointer;
    typedef T& reference;
};

// 针对原生之 指向常量的指针 而设计的 traits 偏特化版
template <class T>
struct iterator_traits<const T*> {
    
    
    typedef random_access_iterator_tag iterator_category;
    typedef T value_type;
    typedef ptrdiff_t difference_type;
    typedef const T* pointer;
    typedef const T& reference;
};

// 这个函数可以很方便地决定某个迭代器的类型 (category)
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&) {
    
    
    typedef typename iterator_traits<Iterator>::iterator_category category;
    return category();
}
// 如果尝试直接写成如下方式:
// return iterator_traits<Iterator>::iterator_category();
// 编译器会将 iterator_traits<Iterator>::iterator_category 解释为一个成员,而不是一个对象(类)
// 而在 C++ 中,类型不能直接实例化,必须通过一个对象的形式返回。因此,我们需要明确地使用一个别名(typedef 或者 using)来定义 category,然后再调用 category() 来创建对象
// 这样写也行
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&) {
    
    
    return typename iterator_traits<Iterator>::iterator_category();
}


// 这个函数可以很方便地决定某个迭代器的 distance type
// 返回类型是一个指向 Iterator 的 difference_type 类型的指针
// 0 是空指针
// 使用 static_cast 将 0 转换为指向 difference_type 的指针类型,即 typename iterator_traits<Iterator>::difference_type*
// 这个函数的主要用途是 用于类型推导,它返回的指针 可以用于编译期类型检查,并不在运行时起实际作用
// 使用 typename std::iterator_traits<Iterator>::difference_type* diff_type_ptr = distance_type(first);
template <class Iterator>
inline typename iterator_traits<Iterator>::difference_type*
distance_type(const Iterator&) {
    
    
    return static_cast<typename iterator_traits<Iterator>::difference_type*>(0);
}

// 这个函数可以很方便地决定某个迭代器的 value type
template <class Iterator>
inline typename iterator_traits<Iterator>::value_type*
value_type(const Iterator&) {
    
    
    return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

// 以下是整组 distance 函数
template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
_distance(InputIterator first, InputIterator last,
          input_iterator_tag) {
    
    
    iterator_traits<InputIterator>::difference_type n = 0;
    while (first != last) {
    
    
        ++first; ++n;
    }
    return n;
}

template <class RandomAccessIterator>
inline iterator_traits<RandomAccessIterator>::difference_type
__distance(RandomAccessIterator first, RandomAccessIterator last,
          random_access_iterator_tag) {
    
    
    return last - first;
}

template <class InputIterator>
inline iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last) {
    
    
    typedef typename
        iterator_traits<InputIterator>::iterator_category category;
    return __distance(first, last, category());
}

// 以下是整组 advance 函数
template <class InputIterator, class Distance>
inline void __advance(InputIterator& i, Distance n,
                      input_iterator_tag) {
    
    
    while (n--) ++i;
}

template <class BidirectionalIterator, class Distance>
inline void __advance(BidirectionalIterator& i, Distance n,
                      bidirectional_iterator_tag) {
    
    
    if (n >= 0)
        while (n--) ++i;
    else
        while (n++) --i;
}

template <class RandomAccessIterator, class Distance>
inline void __advance(RandomAccessIterator& i, Distance n,
                      random_access_iterator_tag) {
    
    
    i += n;
}

template <class InputIterator, class Distance>
inline void advance(InputIterator& i, Distance n) {
    
    
    __advance(i, n, iterator_category(i));
}

前置 ++ 操作符

class Example {
    
    
public:
    Example& operator++(); // 前置++
};

Example& Example::operator++() {
    
    
    // 对象自身进行递增操作
    ++value; // 假设类中有一个成员变量 value
    return *this; // 返回自身的引用
}

Example e;
++e; // 对象 e 的值会先递增,然后返回自身的引用

后置 ++ 操作符

class Example {
    
    
public:
    Example operator++(int); // 后置++
};

Example Example::operator++(int) {
    
    
    Example temp = *this; // 保存操作前的对象副本
    ++value; // 对象自身进行递增操作
    return temp; // 返回未递增前的副本
}

Example e;
e++; // 对象 e 的值会先返回递增前的状态,然后对自身进行递增

7、SGI STL 的私房菜:__type_traits

1、STL 只对迭代器加以规范,制定出 iterator_traits 这样的东西。SGI 把这种技法 进一步扩大到迭代器以外的世界,于是有了所谓的 __type_traits。双底线前缀词意指 这是 SGI STL 内部所用的东西,不在 STL 标准规范之内

iterator_traits 负责萃取迭代器的特性,__type_traits 则负责萃取型别(type)的特性

2、这个型别是否具备 非平凡的默认构造函数(不是自动生成的构造函数)?是否具备 非平凡的拷贝构造函数?是否具备 非平凡的赋值操作符? 是否具备 非平凡的析构函数? 如果答案是否定的,在对这个型别进行构造、析构、拷贝、赋值等操作时,就可以采用最有效率的措施(例如根本不调用 constructor, destructor),而采用内存直接处理操作如 malloc()、memcpy() 等等,获得最高效率。这对于大规模而操作频繁的容器,有着显著的效率提升

一个平凡的默认构造函数是指,编译器生成的构造函数 仅仅是简单地分配内存给对象,而不需要做其他工作
这种类型的对象的成员变量都可以是内置类型(如 int、float 等),或者它们的构造函数也都是平凡的

对于平凡的类型,直接使用 这些低级操作 来分配和初始化内存 可以节省调用构造函数的开销,因为这些类型的构造函数 不会执行额外的初始化操作
例如,如果一个类型 T 的默认构造函数 是平凡的,可以通过 malloc(sizeof(T)) 为一个对象分配内存,而不需要 显式地调用 new T() 来创建对象

避免不必要的初始化:在某些情况下,希望通过 malloc() 或 memset() 只分配对象所需的内存,而不执行初始化操作(例如,后续我们会手动初始化对象)。对于平凡的类型,这样的操作是安全的,因为 它们不需要额外的构造逻辑

// 假设 有一个简单的类:
struct Point {
    
    
    int x;
    int y;
};

// 使用 malloc() 分配内存
#include <iostream>
#include <cstdlib>  // for malloc and free

int main() {
    
    
    // 使用 malloc 分配一个 Point 对象的内存
    Point* p = (Point*)malloc(sizeof(Point));

    // 直接访问 p 的成员变量
    p->x = 10;
    p->y = 20;

    std::cout << "Point: (" << p->x << ", " << p->y << ")" << std::endl;

    // 使用完后释放内存
    free(p);

    return 0;
}

使用 memset() 初始化:
假设 要将一个 Point 对象的所有成员变量都初始化为 0:

#include <iostream>
#include <cstring>  // for memset

int main() {
    
    
    Point p;
    // 使用 memset 将 p 的内存全部置为 0
    std::memset(&p, 0, sizeof(Point));
    std::cout << "Point after memset: (" << p.x << ", " << p.y << ")" << std::endl;
    return 0;
}

使用 memset() 将 Point 对象 p 的内存全部置为 0。对于 int 类型的成员变量,这意味着 它们都被初始化为 0。这种方法很快,因为 memset() 是一个低级的内存操作,而不是 通过调用构造函数来逐个初始化成员

一个平凡的默认构造函数 是由编译器自动生成的,且 不会执行任何额外的初始化操作(不会为对象的成员变量 赋初值(即内置类型的成员变量 会是未初始化状态))

Point* p = (Point*)malloc(sizeof(Point));

平凡的拷贝构造函数 是指由编译器生成的,用于 按位拷贝对象内容的构造函数(意味着 它只是简单地将对象的内存 内容逐字节复制到新的对象中,而不涉及 深拷贝或特殊逻辑)

#include <iostream>
#include <cstring> // for memcpy

struct TrivialCopy {
    
    
    int x;
    double y;
    char z;
};

// 编译器会为 TrivialCopy 生成一个平凡的拷贝构造函数

int main() {
    
    
    TrivialCopy obj1 = {
    
    1, 2.5, 'a'};
    TrivialCopy obj2;

    // 使用 memcpy 直接拷贝 obj1 到 obj2
    std::memcpy(&obj2, &obj1, sizeof(TrivialCopy));

    std::cout << "obj2.x = " << obj2.x << ", obj2.y = " << obj2.y << ", obj2.z = " << obj2.z << std::endl;

    return 0;
}

平凡的赋值操作符 会简单地将一个对象的每个成员变量 逐个赋值给 另一个对象的对应成员(可以直接对 对象进行内存级别的赋值,而不需要 调用用户自定义的赋值操作符)

// 跟上面是一样的
#include <iostream>
#include <cstring> // for memcpy

struct TrivialAssign {
    
    
    int a;
    float b;
    char c;
};

// 编译器会为 TrivialAssign 生成一个平凡的赋值操作符

int main() {
    
    
    TrivialAssign obj1 = {
    
    42, 3.14f, 'x'};
    TrivialAssign obj2;

    // 使用 memcpy 进行赋值
    std::memcpy(&obj2, &obj1, sizeof(TrivialAssign));

    std::cout << "obj2.a = " << obj2.a << ", obj2.b = " << obj2.b << ", obj2.c = " << obj2.c << std::endl;

    return 0;
}

平凡的析构函数 不会做任何清理工作,它仅仅 表示对象的生命周期结束,并 允许内存被释放,编译器在销毁对象时,不需要调用复杂的析构逻辑,从而可以直接释放内存(可以通过 free() 这样的内存管理函数 直接释放内存,从而 避免不必要的性能损失)

// 直接使用 free() 释放内存,而不调用析构函数
free(p);

3、定义于 SGI <type_traits.h> 中的 __type_traits,提供了一种机制,允许 针对不同的 型别属性(type attributes),在编译时期 完成函数派送决定
这对于撰写 template 很有帮助,例如,当准备对一个 “元素型别未知” 的数组执行 copy 操作时,如果能事先知道其元素型别是否有一个 平凡拷贝构造函数,便能够帮助 决定是否可使用快速的 memcpy() 或 memmove()

希望,程序之中可以这样运用 __type_traits<T>,T 代表任意型别:

__type_traits<T>::has_trivial_default_constructor
__type_traits<T>::has_trivial_copy_constructor
__type_traits<T>::has_trivial_assignment_operator
__type_traits<T>::has_trivial_destructor
__type_traits<T>::is_POD_type    
// POD : 简单数据结构。它指的是 没有复杂构造函数、析构函数 或 复制控制成员,并且 所有成员都是公有的数据类型

希望 上述式子 响应我们 “真” 或 “假”(以便 决定采取什么策略),但其结果 不应该只是个 bool 值,应该是个有着 真/假 性质的“对象”,因为 希望利用其响应结果来 进行参数推导, 而编译器 只有面对 类对象(根据类创建了 一个具体的实例) 形式的参数,才会做参数推导

为此,上述式子 应该传回这样的东西:

struct __true_type {
    
     };
struct __false_type {
    
     };

这两个空白 类 没有任何成员,不会带来额外负担,却又能够标示真假

为了达成 上述五个式子,__type_traits 内必须定义一些 typedef,其值不是 __true_type 就是 __false_type

template <class type>
struct __type_traits {
    
    
    typedef __true_type this_dummy_member_must_be_first;
    /* 不要移除这个成员。它通知有能力自动将 __type_traits 特化的编译器说,现在所看到的这个 __type_traits template 是特殊的。这是为了确保万一编译器也使用一个名为 __type_traits 而其实与此处定义并无任何关联的 template 时,所有事情都仍将顺利运作
    其作用是通知编译器,这个 __type_traits 结构体有特定的用途,而不仅仅是用户定义的普通模板,这样,即使编译器自身也有一个 __type_traits 模板(用于某些内部优化),也不会与用户自定义的模板冲突 */

    /* 以下条件应被遵守,因为编译器有可能自动为各型别产生专属的 __type_traits 特化版本:
        - 你可以重新排列以下的成员次序
        - 你可以移除以下任何成员
        - 绝对不可以将以下成员重新命名而却没有改变编译器中的对应名称
        - 新加入的成员会被视为一般成员,除非你在编译器中加上适当支持*/

    typedef __false_type has_trivial_default_constructor;
    typedef __false_type has_trivial_copy_constructor;
    typedef __false_type has_trivial_assignment_operator;
    typedef __false_type has_trivial_destructor;
    typedef __false_type is_POD_type;
};

为什么 SGI 把所有内嵌型别 都定义为 __false_type 呢
SGI 定义出最保守的值,然后(稍后可见)再针对每一个标量类型(只能表示单个数值,并且可以 直接存储在寄存器中)设计适当的 __type_traits 特化版本

标量类型包括以下几类:
1)算术类型:
包括所有的整型(如 int、unsigned int、long、char)和浮点型(如 float、double)
这些类型 表示整数和浮点数,都是单一的数值

2)指针类型:
所有指针类型(如 int*、double*)也被视为标量类型,因为 存储的只是一个内存地址(即某个对象或变量的地址)
指针 可以直接比较、赋值和进行算术运算(如指针加减)

3)枚举类型:
枚举类型 是用户定义的类型,包含一组整型常量。虽然是 用户自定义的,但它们的底层实现 是整数,所以也属于标量类型

enum Color {
    
     RED, GREEN, BLUE };

4)std::nullptr_t 类型
nullptr 是 C++11 引入的一种类型,用于表示空指针

赋值与比较:标量类型 可以直接进行赋值和比较操作,通常这些操作 都是内置的。例如:
整数类型的比较(如 a < b)是直接的数值比较
指针的比较(如 ptr1 == ptr2)比较的是 它们所指向的内存地址
直接初始化:标量类型的对象 可以通过直接的字面值 进行初始化,例如 int x = 5; 或者 double y = 3.14;

某些编译器(如 Silicon Graphics N32 和 N64 编译器)会自动 为所有型别提供适当的特化版本

4、这些定义 对于内建有 __types_traits 支持能力的编译器(例如 Silicon Graphics N32 和 N64)并无伤害,对于无该等支持能力的编译器而言,则属必要

/* 以下针对 C++ 基本型别 char, signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, float, double, long double 提供特化版本。注意,每一个成员的值都是 __true_type,表示这些型别都可采用最快速方式(例如 memcpy)来进行拷贝(copy)或赋值(assign)操作 */

// __STL_TEMPLATE_NULL 被定义为 template<>,用于声明模板类的显式特化

__STL_TEMPLATE_NULL struct __type_traits<char> {
    
    
    typedef __true_type has_trivial_default_constructor;// 自动生成的
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

__STL_TEMPLATE_NULL struct __type_traits<signed char> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

__STL_TEMPLATE_NULL struct __type_traits<unsigned char> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

__STL_TEMPLATE_NULL struct __type_traits<short> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

__STL_TEMPLATE_NULL struct __type_traits<unsigned short> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;

_STL_TEMPLATE_NULL struct __type_traits<int> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

_STL_TEMPLATE_NULL struct __type_traits<unsigned int> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

_STL_TEMPLATE_NULL struct __type_traits<long> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

_STL_TEMPLATE_NULL struct __type_traits<unsigned long> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

_STL_TEMPLATE_NULL struct __type_traits<float> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

_STL_TEMPLATE_NULL struct __type_traits<double> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
	typedef __true_type is_POD_type;
};

_STL_TEMPLATE_NULL struct __type_traits<long double> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
	typedef __true_type is_POD_type;
};

// 注意,以下针对原生指针设计 __type_traits 偏特化版本
// 原生指针亦被视为一种标量型别
template <class T>
struct __type_traits<T*> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __true_type has_trivial_copy_constructor;
    typedef __true_type has_trivial_assignment_operator;
    typedef __true_type has_trivial_destructor;
    typedef __true_type is_POD_type;
};

uninitialized_fill_n() 全局函数:

template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first,
                                            Size n, const T& x) {
    
    
    return __uninitialized_fill_n(first, n, x, value_type(first));
}

该函数以 x 为蓝本,自迭代器 first 开始构造 n 个元素。为求取最大效率,首先以 value_type() 萃取出迭代器 first 的 value type,再利用 __type_traits 判断该型别是否为 POD 型别:

template <class ForwardIterator, class Size, class T, class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first,
                                              Size n, const T& x, T1*)
{
    
    
    typedef typename __type_traits<T1>::is_POD_type is_POD;
    return __uninitialized_fill_n_aux(first, n, x, is_POD());
}

以下就 “是否为 POD 型别” 采取最适当的措施:

// 如果不是 POD 型别,就会派送到这里
template <class ForwardIterator, class Size, class T>
ForwardIterator

__uninitialized_fill_n_aux(ForwardIterator first, Size n,
                              const T& x, __false_type) {
    
    
    ForwardIterator cur = first;
    // 为求阅读顺畅简化,以下将原本有的异常处理去除
    for ( ; n > 0; --n, ++cur)
        construct(&*cur, x);     
    return cur;
}

// 如果是 POD 型别,就会派送到这里。下两行是原文件所附注解
// 如果 复制构造 等同于 赋值,而且有 平凡的析构函数,
// 以下就有效
template <class ForwardIterator, class Size, class T>
inline ForwardIterator
__uninitialized_fill_n_aux(ForwardIterator first, Size n,
                              const T& x, __true_type) {
    
    
    return fill_n(first, n, x);   // 交由高阶函数执行,如下所示
}

// 以下是定义于 <stl_algobase.h> 中的 fill_n()
template <class OutputIterator, class Size, class T>
OutputIterator fill_n(OutputIterator first, Size n, const T& value) {
    
    
    for ( ; n > 0; --n, ++first)
        *first = value;
    return first;
}

对于非 POD 类型:
必须在未初始化的内存上调用 construct(&*cur, x),以正确地构造对象,执行必要的初始化逻辑
construct 通常是一个函数模板,内部调用了对象的拷贝构造函数

对于 POD 类型:
可以直接使用 fill_n,在未初始化的内存上 直接进行赋值操作 *first = value
因为 POD 类型的赋值操作 和拷贝构造等价,且析构函数是平凡的,所以这样做是安全的

对于 POD 类型,复制构造函数(即通过拷贝构造函数创建对象)和 赋值操作符(即通过赋值操作更新对象)的行为是等价的。
两者 都只是简单地将源对象的内存内容 复制到目标对象,不涉及 复杂的深拷贝或资源管理

非 POD 类型的示例

#include <iostream>

class NonPODType {
    
    
public:
    NonPODType() {
    
     std::cout << "Default constructor\n"; }
    NonPODType(const NonPODType&) {
    
     std::cout << "Copy constructor\n"; }
    NonPODType& operator=(const NonPODType&) {
    
    
        std::cout << "Assignment operator\n";
        return *this;
    }
    ~NonPODType() {
    
     std::cout << "Destructor\n"; }
};

int main() {
    
    
    NonPODType* arr = static_cast<NonPODType*>(operator new[](3 * sizeof(NonPODType)));
    NonPODType value;

    // 必须逐个构造
    for (int i = 0; i < 3; ++i) {
    
    
        new (arr + i) NonPODType(value);  // 调用拷贝构造函数
    }

    // ...

    // 必须逐个析构
    for (int i = 0; i < 3; ++i) {
    
    
        (arr + i)->~NonPODType();  // 调用析构函数
    }

    operator delete[](arr);
    return 0;
}

for (int i = 0; i < 3; ++i) {
    
    
    new (arr + i) NonPODType(value);  // 调用拷贝构造函数
}

在前面的代码中,使用了 operator new[] 分配了一块原始的、未初始化的内存(分配足够的内存,但不调用构造函数),足以存放 3 个 NonPODType 对象:

NonPODType* arr = static_cast<NonPODType*>(operator new[](3 * sizeof(NonPODType)));

new (arr + i) NonPODType(value); 的含义
定位 new:
new 后跟一对括号,里面是一个指针,表示 在指定的内存地址上构造对象,而不是 分配新的内存
语法形式为:

void* operator new(size_t, void* ptr) noexcept;

arr + i
arr 是指向 NonPODType 的指针,arr + i 表示数组中第 i 个元素的位置
由于 NonPODType 的大小已知,指针运算会正确地计算出偏移量
NonPODType(value)
这是构造对象的方式,使用了 NonPODType 的拷贝构造函数,以 value 为参数
value 是一个已存在的 NonPODType 对象
综合起来:
new (arr + i) NonPODType(value); 的作用是在内存地址 arr + i 处,使用 value 调用 NonPODType 的拷贝构造函数,构造一个新的对象
定位 new 的作用:允许在已分配的内存上构造对象,而不重新分配内存

POD 类型的示例

#include <iostream>
#include <cstring>  // for memcpy

struct PODType {
    
    
    int a;
    double b;
};

int main() {
    
    
    PODType* arr = static_cast<PODType*>(operator new[](3 * sizeof(PODType)));
    PODType value = {
    
    1, 2.0};

    // 可以直接赋值,无需调用构造函数
    for (int i = 0; i < 3; ++i) {
    
    
        std::memcpy(arr + i, &value, sizeof(PODType));
    }

    // 使用完毕后,直接释放内存,无需调用析构函数
    operator delete[](arr);
    return 0;
}

5、大部分 缺乏这种特异功能的编译器而言,__type_traits 针对 shape 萃取出来的每一个特性都是 __false_type,即使 shape 是个 POD 型别

这样的结果 当然过于保守,但是别无选择,除非我针对 shape,自行设计一个 __type_traits 特化版本,明白地告诉编译器以下事实(举例):

template<> struct __type_traits<Shape> {
    
    
    typedef __true_type has_trivial_default_constructor;
    typedef __false_type has_trivial_copy_constructor;
    typedef __false_type has_trivial_assignment_operator;
    typedef __false_type has_trivial_destructor;
    typedef __false_type is_POD_type;
};

究竟一个 class 什么时候该有自己的 non-trivial default constructor, non-trivial copy constructor, non-trivial assignment operator, non-trivial destructor 呢?一个简单的判断准则是:如果 class 内含指针成员,并且 对它进行内存动态配置,那么这个 class 就需要实现自己的 non-trivial-xxx

至少,有了这个 __type_traits 之后,当 设计新的泛型算法时,面对 C++ 标量型别,便有足够的信息 决定采用最有效的拷贝操作或赋值操作 —— 因为每一个标量型别 都有对应的 __type_traits 特化版本,其中 每一个 typedef 的值都是 __true_type

猜你喜欢

转载自blog.csdn.net/AsherGu/article/details/143190446