auto_ptr
1 | void remodel(string & str) { |
这段代码会造成内存泄漏,我们会想说在函数return
之前记得delete ps
即可避免内存泄漏。
1 | void remodel(string & str) { |
但是这段代码在exception被抛出之后,delete
这个句子不会被执行,也就造成了内存泄漏。
使用一个auto_ptr
类型的话,在函数返回时,因为auto_ptr
有自己的析构函数,所以会自己释放自己指向的内存空间。auto_ptr
不能用于数组。
You should use an auto_ptr object only for memory allocated by new, not for memory allocated by new [] or by simply declaring a variable.
赋值操作
在正常的指针操作中,指针指间的相互赋值实际上是让多个指针同时指向一个内存空间。但是这个应用到auto_ptr
上时就会有问题了,但删除两个auto_ptr
时会导致程序对同一个内存空间进行了多次的delete
操作。
为了避免这一个问题,可以用下面几个策略来避免:
- 让赋值操作变成
deep copy
,这会让两个指针指向不同的内存空间,其中一个将作为另一个的拷贝。 - 构建
ownership
概念,也就是拥有者
概念。一个对象只能有一个智能指针拥有,只有拥有这个对象的智能指针能析构这个对象。赋值的同时转换拥有权。 - 创建一个更加智能的指针,对指向特定对象的指针进行跟踪。这个叫做
reference counting
。 - 当一个指针放弃了自己的拥有权之后,它可能会转变为不可用的状态。
iterator 是一种smart pointer
explicit
在类声明中使用这个可以避免隐形的转换。例如
1 | class Star |
偏特化
两个说法
- 提供一份template定义式,其本身仍然是templaized。
- 针对任何template参数更进一步的条件限制所设计出来的一个特化版本。
例如下面的代码接受任意类型
1 | template <typename T> |
而下面的代码只接受原生指针
1 | template <typename T> |
下面是一段用于类型萃取的代码
1 | template <class I> |
如果class I有自己的value_type
的话
1 | template<class I> |
但是这个萃取器对于原生指针,像int* I
这样的类型并不起作用,因为它并不是由用户定义的,因此并没有在内部定义一个value_type
。因此需要一个特化版本的iterator_traits
。如下所示
1 | template<typename T> |
即使有这两个萃取器,对于像指向const int的指针类型的时候,我们只能获得一个无法更改的返回值。因此我们还需要另一个特化的萃取器。
1 | template<typename T> |
最常用的迭代器(iterator)类型有物种:
- value type
- difference type
- pointer
- reference
- iterator catagoly
3.4.1 value type
任何一个打算与STL配合的class都需要定义自己的value type
内嵌型别。
3.4.2 difference type
difference type
表示两个迭代器之间的距离。因此它可以用来表示一个容器的最大容量。如果需要提供一个计数功能count()
的话,返回值就需要是difference type
1 | template<class I,class T> |
接着可以给出一个泛化版本的和两个特化版本的difference type
的萃取器。
1 | //泛化版本 |
1 | //为原生指针特化的版本 |
1 | //为pointer to const偏特化的 |
有了这三个萃取器,我们想要任何迭代器的difference type
的时候我们只需要一下代码:
1 | typename iterator_traits<class I>::difference_type; |
reference type
从迭代器所指之物的内容是否允许改变来看,可以将迭代器分为两种:
- 不允许改变所指对象之内容,称为
constant iterators
。例如const int* pic
。 - 允许改变所指对象之内容,称为
mutable iterators
。例如int* pi
。
当p
是一个mutable iterators
时,如果其value type
是T
,那么*p
的型别不应是T
,而应该是&T
。以此类推,如果p
是一个constant iter
,其value type
是T
,那么*p
的型别应该是const T&
。这里所讨论的*p
就是所谓的reference type
。
pointer type
1 | Item& operator* () const{ return *ptr;} |
Item&
便是reference type
,而Item*
便是其pointer type
。
直接献上完整的代码(Orz):
1 | template <class Iterator> |
iterator_category
根据移动特性与施行操作,迭代器被分为五类:
- input iterator:这种迭代器所指的对象,不允许外界改变。只读(read only)。
- output iterator:write only。
- forward iterator:允许“写入型”算法(例如replace())在此迭代器所形成的区间上进行读写操作。
- bidirectional iterator:可双向移动。某些算法需要逆向走访某个迭代器区间(例如逆向拷贝某范围内的元素),可以使用这种迭代器。
- random access iterator:前四种迭代器都只供应一部分指针算数能力(前三种支持operator++,第四种再加上operator–),第五种则涵盖所有指针算数能力,包括
p+n`
p-np[n]
p1-p2p1<p2`。
advance()
这是许多算法内部常用的一个函数,该函数有两个参数p
和n
。表示函数将p
累进n
次。下面有三份定义,分别针对Input Iterator`
Bidirectional IteratorRandom Access Iterator
。而对
ForwardIterator来说,它的版本与
Input Iterator`的版本一致。
1 | template <class InputIterator, class Distance> |
1 | template <class BidirectionalIterator, class Distance> |
1 | template <class RandomAccessIterator, class Distance> |
但是这样的话我们还需要一个函数来判断迭代器的类型然后调用相应的函数。
定义五个classes
1 | struct input_iterator_tag {}; |
设计五个用于内部调用的函数,最后一个参数只是为了激活函数的重载。
1 | template <class InputIterator, class Distance> |
1 | template <class BidirectionalIterator, class Distance> |
1 | template <class RandomAccessIterator, class Distance> |
这里使用的方法是添加一个单纯的调用函数advance()
,这个函数只接受两个参数,当它将工作转向__advance()
的时候,自行通过traits
机制加上第三个参数,也就是前面定义的五个classes
。
1 | template <class InputIterator, class Distance> |
然后再定义函数iterator_category()
。
1 | template <class Iterator> |
可以注意到我们在列举__advance()
函数的时候,少列举了两种,一种是OutputIterator
,另一种是ForwardIterator
。前者是因为可以同InputIterator
共用,后者是因为可以消除单纯只做传递调用的函数,所以就没有对应的__advance()
函数了。
__types_traits
这个技巧类似于前面的iterator_traits
,都是用于解析特征。这里需要解析看这个类型能否使用更快速的内存上的操作进行操作,避免使用高层次的函数。
使用场景:uninitialized_fill_n()
1 | template <class ForwardIterator, class Size, class T> |
上面这个函数以x
为蓝本,自迭代器first
开始构造n
个元素。为求取最大效率,首先以value_type()
萃取出迭代器first
的value type
,再利用__type_traits
判断该型别是否为POD
型别:
1 | template <class ForwardIterator, class Size, class T, class T1> |
接着调用了上面这个函数,其中is_POD()是一个struct
,然后调__uninitialized_fill_n_aux
函数,其中编译器会根据is_POD()
来重载函数,分配到是POD
和不是POD
对应的函数中。