继上篇文章学习了如何构造容器后, 我们将学习如何析构容器, 同时实现一些工具函数用于构造与析构。
首先创建一个头文件"alloc_destroy.h", 用来存放下面实现的构造与析构的工具函数
1.construct()
template<typename T1, typename T2>
void construct(T1 *p, const T2& value)
{
new (p) T1(value);
}
我们先来活用之前学过的定位new运算符,实现construt()函数。construct()在指针p的位置调用构造函数创建一个对象,我们以后将使用construct()构造单个对象。
2.destroy()
我们需要重载好几个destroy()函数,先来看一个最简单的
template<typename T>
void destroy(T *p)
{
p->~T();
}
这个没什么好说的,在指针p所指位置调用析构函数来释放一个对象。但我们的容器一般都有多个元素,肯定不能一个一个调用destroy(p),那么我们肯定需要一个能直接析构一个区间的destroy(begin, end):
template<typename ForwardIterator>
void destroy(ForwardIterator begin, ForwardIterator end)
{
aux_destroy(begin, end, std::is_trivially_destructible<decltype(*begin)>());
}
template<typename ForwardIterator>
void aux_destroy(ForwardIterator begin, ForwardIterator end, std::true_type)
{
}
template<typename ForwardIterator>
void aux_destroy(ForwardIterator begin, ForwardIterator end, std::false_type)
{
for (; begin != end; ++begin) {
destroy(&*begin);
}
}
对于没深入了解过模板的人来说上面的代码可能有点难以理解,但不要急,我们一步一步来。
我们的目的是实现函数 destroy(ForwardIterator begin, ForwardIterator end),我们先来看看它的内部代码:
aux_destroy(begin, end, std::is_trivially_destructible<decltype(*begin)>());
它调用了一个辅助函数aux_destroy,同时将区间参数向下传递。但我们最迷惑的肯定是std::is_trivially_destructible<decltype(*begin)>()
,这的确有点复杂,我们可以从内向外分解它。
decltype(*begin): begin即得到迭代器begin所指的元素, 而decltype()可以得到内部元素的类型,合起来就是容器内部元素的类型(因为begin的类型与容器内其他元素的类型相同)。
std::is_trivially_destructible<T> : std::is_trivially_destructible是一个类模板,用来判断类型T的析构函数是否无关紧要(trivially)。那什么样的类的析构函数是trivial的?:
1. 使用隐式定义的析构函数,即没有定义自己析构函数
2. 析构函数不是虚函数
3. 其基类与非静态成员也是可trivially析构的
当同时满足上面3个条件时,我们就可称类型T是 trivially_destructible
说了那么多, 我们来举2个例子。
class Test
{
private:
int i;
public:
Test(int i) {
this->i = i;
}
};
Test这个类就满足之前的3个条件,所以它是trivially_destructible。即我们调不调用析构函数都无所谓,因为它本质上并没有任何需要释放的资源。就像当我们要释放std::vector<int>类型的容器时,我们并不需要在意Int类型元素的释放,释放vector<Test>也是一样的道理。
再来看一个反例:
class Test
{
private:
int i;
int *p;
public:
Test(int i) {
this->i = i;
p = new int;
}
~Test() {
delete p;
}
};
很容易发现Test类现在多了一个指针变量p,并且p再构造函数被赋值了。为了保证内存不泄漏,我们必须手动定义析构函数释放p。很显然现在Test类已经不是trivially destructible了,因为我们定义了自己的析构函数。此时它的析构函数已经不再无关紧要,因为必须要调用析构函数来释放内存。这个时候释放std::vector<Test>对象时, 我们必须要为每个元素析构,这是库作者必须要考虑到的事情。
进一步, 当T被判断是trivially_destructible时, 类型std::is_trivially_destructible<T>将被推导为 std::true_type, 反之则为std::false_type。
std::is_trivially_destructible<decltype(*begin)>():
现在我们再来看是不是简单了一点呢? 这段代码意思即为判断元素类型是否是trivially_destructible。如果是,类型被转化为std::true_type,反之std::false_type。 最后再调用构造函数构造出std::true_type或std::false_type的对象。
接下来就简单了,编译器将根据返回的是std::true_type还是std::false_type调用不同的辅助函数aux_destroy():
1. 当参数为std::true_type时
可以看到我们的函数体为空,即我们什么都不做,因为类型的析构函数无关痛痒,没有任何影响,只需系统自动回收
2.当参数为std::false_type时
这时我们必须为每个元素调用构造函数了,为此使用循环调用我们之前实现的destroy(T *p)。
template<typename ForwardIterator>
void aux_destroy(ForwardIterator begin, ForwardIterator end, std::false_type)
{
for (; begin != end; ++begin) {
destroy(&*begin);
}
}
终于,我们实现了想要的destroy(ForwardIterator begin, ForwardIterator end),它能够帮助我们析构[begin, end]范围的所有元素。
好了,现在看看我们手上有了哪些工具:
1.
template<typename T1, typename T2>
void construct(T1 *p, const T2& value);
它可以帮助我们在p的位置上调用构造函数
2.
template<typename T>
void destroy(T *p);
它能够析构p所指的对象
3.
template<typename ForwardIterator>
void destroy(ForwardIterator begin, ForwardIterator end);
它能够析构[begin, end)范围的所有对象
我们接下来将运用这些函数,以及之后要讲的分配器一起来实现容器的构造与析构。
好了,这篇文章也到此结束啦。下一篇就真的要开始讲Allocator,总算要进入正题了,真是不容易。不过Allocator的难度也很高,应该要说很久,还是请做好准备吧。
最后附上本篇文章的代码地址:https://github.com/Puppas/STL-in-Cpp11/blob/master/STL/alloc_destroy.h
以及github地址:https://github.com/Puppas/STL-in-Cpp11
P.S 谢绝转载,谢谢