前言

本节将会分析 algobase.h 中的算法,algobase.h 中存放的都是一些比较基础的算法,如 max()min()copy() 等,这里就挑其中最具有代表性的 copy() 函数来分析,除了 copy() 外还有许多如 copy_backward()move()move_backward() 等函数,STL 对这些函数效率上的优化方式与 copy() 的处理基本是一致的,就不再分析。

copy

不论是对客端程序或对 STL 内部而言,copy() 都是一个常常被调用的函数。由于 copy 进行的是复制操作,而复制操作不外乎运用 assignment operatorcopy constructor (copy算法用的是前者),但是某些元素型别拥有的是 trivialassignment operator,因此,如果能够使用内存直接复制行为(例如标准函数 memmovememcpy),便能够节省大量时间。为此,SGI STL的copy 算法用尽各种办法,包括函数重载(function overloading)、型别特性(type traits)、偏特化(partial specialization)等编程技巧,无所不用其极地加强效率。下图展示了整个 copy() 操作的脉络。

7.png

从上面的流程可以大概梳理出 copy() 的调用脉络。

首先,copy 函数本身除了一个模板函数的泛化版本,还为 char* 以及 wchar_t* 提供了两个特化版本,针对这两种类型的 copy 直接调用 memmove(),其余类型元素的 copy 均转调用 __copy_dispatch

__copy_dispath 有一个完全泛化版本以及两个针对原生指针 T*const T* 的偏特化版本;

  • 完全泛化版本的 __copy_dispatch 根据输入的迭代器类型转调用 __copy__copy 有处理 input_iteratorrandom_access_iterator 的两个重载,均会针对迭代器类型采取最有效率的措施。
  • 针对原生指针的 __copy_dispatch 会根据指针所指元素的 is_trivially_copy_assignable 特性来决定拷贝策略,若为平凡类型则直接调用 memmove(),否则转调用 __copy_d

可以看到 STL 中的 copy 函数的实现确实复杂,通过函数重载、偏特化、型别特性等一系列手段对不同的类别采取不同的方法,但这也是为了效率的最优,it deserves。

上面这样说明还是有些抽象,下面列出 TinySTL 中实现的代码并且在每个函数中都加入了输入,配合测试案例一起理解会清晰许多。

MyTinySTL 中对于 algobase.h 中函数的具体实现与 STL 中还是有不小的差距,因此 copy 这部分还是遵循 STL 的逻辑来实现与分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/// @brief 完全泛化版本 copy
template <class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result) {
std::cout << "copy" << std::endl;
return __copy_dispatch(first, last, result);
}

/// @brief 特殊版本(1),针对 char* 的特化
char* copy(const char* first, const char* last, char* result) {
std::memmove(result, first, last - first);
std::cout << "char* copy" << std::endl;
return result + (last - first);
}

/// @brief 特殊版本(2),针对 wchar_t* 的特化
wchar_t* copy(const wchar_t* first, const wchar_t* last, wchar_t* result) {
std::memmove(result, first, sizeof(wchar_t) * (last - first));
std::cout << "wchar_t* copy" << std::endl;
return result + (last - first);
}

/// @brief copy_dispatch 的完全泛化版本
template <class InputIterator, class OutputIterator>
OutputIterator __copy_dispatch(InputIterator first, InputIterator last, OutputIterator result) {
std::cout << "InputIterator __copy_dispatch" << std::endl;
return __copy(first, last, result, iterator_category(first));
};

/// @brief copy_dispatch 的偏特化版本(1),针对 T*
template <class T>
T* __copy_dispatch(T* first, T* last, T* result) {
using trivial = typename std::is_trivially_copy_assignable<T>::type;
std::cout << "T* __copy_dispatch" << std::endl;
return __copy_t(first, last, result, trivial());
};

/// @brief copy_dispatch 的偏特化版本(2),针对 const T*
template <class T>
T* __copy_dispatch(const T* first, const T* last, T* result) {
using trivial = typename std::is_trivially_copy_assignable<T>::type;
std::cout << "const T* __copy_dispatch" << std::endl;
return __copy_t(first, last, result, trivial());
};

/// @brief __copy 的 InputIterator 的版本,以迭代器是否相等作为循环结束的条件,效率较低
template <class InputIterator, class OutputIterator>
OutputIterator __copy(InputIterator first, InputIterator last,
OutputIterator result, tinystl::input_iterator_tag) {
for (; first != last; ++first, ++result) {
*result = *first;
}
std::cout << "input_iterator_tag __copy" << std::endl;
return result;
}

/// @brief __copy 的 RandomAccessIterator 的版本,以 n 作为循环结束的条件,效率较高
template <class RandomAccessIterator, class OutputIterator>
OutputIterator __copy(RandomAccessIterator first, RandomAccessIterator last,
OutputIterator result, tinystl::random_access_iterator_tag) {
// 又划分出一个函数,为的是其他地方也可以用到
std::cout << "random_access_iterator_tag __copy" << std::endl;
return __copy_d(first, last, result, distance_type(first));
}

/// @brief 针对原生 non_trivally_copy_assignable 指针 和 RandomAccessIterator 的版本
template <class RandomAccessIterator, class OutputIterator, class Distance>
OutputIterator __copy_d(RandomAccessIterator first, RandomAccessIterator last,
OutputIterator result, Distance*) {
for (Distance n = last - first; n > 0; --n, ++first, ++result) {
*result = *first;
}
std::cout << "__copy_d" << std::endl;
return result;
}

/// @brief __copy_t 针对 trivally_copy_assignable 类型的版本
template <class T>
T* __copy_t(const T* first, const T* last, T* result, std::true_type) {
std::memmove(result, first, sizeof(T) * (last - first));
std::cout << "T* trivally_copy_assignable __copy_t" << std::endl;
return result + (last - first);
}

/// @brief __copy_t 针对 non_trivally_copy_assignable 类型的版本
template <class T>
T* __copy_t(const T* first, const T* last, T* result, std::false_type) {
std::cout << "T* non_trivally_copy_assignable __copy_t" << std::endl;
return __copy_d(first, last, result, static_cast<ptrdiff_t*>(nullptr));
}

测试案例

下面的案例中,代码底部的注释即为运行的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tinystl::vector<int> v1{1, 2, 3, 4, 5};
tinystl::vector<int> v2(5);
tinystl::copy(v1.begin(), v1.end(), v2.begin());
for (auto i : v2) {
cout << i << " ";
}
cout << endl;

// copy
// const T* __copy_dispatch
// T* trivally_copy_assignable __copy_t
// copy
// T* __copy_dispatch
// T* trivally_copy_assignable __copy_t
// 1 2 3 4 5

上面的输出还是很耐人寻味的。

首先,为什么明明是 vector<int> 调用的却是 T*copy 方法?答案其实比较明显了,在 vector 容器中说过,vector 的迭代器就是普通的指针,因此这里确实会调用 __copy_t 方法进行拷贝,合理。

其次,为什么调用了两次 copy?这其实是 vector 的初始化列表构造的原因,vector 的初始化列表构造会调用 uninitialized_copy(),从而间接调用 copy(),但这只是在 trivally_copy_assignable 类型的情况下。

当我们不使用初始化列表构造时,就只会输出一次 copy 的信息了。

1
2
3
4
5
6
7
8
9
10
11
12
tinystl::vector<int> v1(5);
tinystl::vector<int> v2(5);
tinystl::copy(v1.begin(), v1.end(), v2.begin());
for (auto i : v2) {
cout << i << " ";
}
cout << endl;

// copy
// T* __copy_dispatch
// T* trivally_copy_assignable __copy_t
// 0 0 0 0 0

我们再定义一个 non-trivial 的类型 A 并进行测试,发现 copy 也会调用相应的版本。

这里有一点比较奇怪,虽然说 SGI-STL 中对于所有的用户自定义类型都采用了最保守的 type traits,但不同版本的 STL 貌似策略并不相同,而且在 TinySTL 中使用的是诸如 std::is_trivially_copy_assignable<T> 来判断 type traits,所以运行结果会与预期的有所差别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {

public:
A() : a("A") {}
A(const A& other) : a(other.a) {}
friend ostream& operator<<(ostream& os, const A& a) {
os << a.a;
return os;
}

private:
string a;
};

tinystl::vector<A> v1(5);
tinystl::vector<A> v2(5);
tinystl::copy(v1.begin(), v1.end(), v2.begin());
for (auto i : v2) {
cout << i << " ";
}
cout << endl;

// copy
// T* __copy_dispatch
// T* non_trivally_copy_assignable __copy_t
// __copy_d
// A A A A A

换一个不是迭代器不为原生指针的容器类型,如 deque,再进行测试,可以发现 copy 也会转调用 random_access_iterator 版本的 __copy_dispatch 重载。

1
2
3
4
5
6
7
8
9
10
11
12
13
tinystl::deque<int> d1(5);
tinystl::deque<int> d2(5);
tinystl::copy(d1.begin(), d1.end(), d2.begin());
for (auto i : d2) {
cout << i << " ";
}
cout << endl;

// copy
// InputIterator __copy_dispatch
// random_access_iterator_tag __copy
// __copy_d
// 0 0 0 0 0

最后是 char* 类型,copy 直接在最外层针对该类型做了特化,不会进一步向下调用。

1
2
3
4
5
6
7
const char s1[] = "hello";
char s2[10];
tinystl::copy(s1, s1 + 5, s2);
cout << s2 << endl;

// char* copy
// hello

总结

这一节简单分析了 algobase.h 中最具有代表性的一个函数:copy,该函数通过各种精妙的设计来实现运行时的效率最优,很好地体现了 STL 中函数的设计思想,algobase.h 中还有许多类似的函数,这里就不一一分析了,下一节将会进入最为庞大的 algo.h 部分的分析。