您的位置:首页 > 编程语言

没有模板代码膨胀的STL:四、一些讨论和个人心得

2015-05-11 07:36 323 查看
总目录

一、设计目标与实现思路

二、使用方法与注意事项

三、开发者指南

四、一些讨论和个人心得

前面3篇文章都是针对POD_STL项目本身的介绍。这篇文章则发散讨论下其他方面的内容。

全功能的去模板化STL是可能的吗?

有些人对POD_STL限制容器元素类型的做法颇有微词。但实际上,即使是标准STL也对容器元素类型有很多限制,而POD_STL只是放弃了对一部分很少使用的类型的支持。当然,人的欲望总是无穷的,我们不妨大胆想象一下:有没有可能实现一个与标准STL功能完全相同(暂时先不考虑allocator),但内部却没有模板代码膨胀的STL版本?

有些人会想:假如真有这种技术,世界上那么多聪明的程序员早就把它给实现了。怎么会把STL广被诟病的模板代码膨胀问题一直留到现在?

但我要说的是,实现这种STL版本还真是可能的。

在本系列第一篇《设计目标与实现思路》中,我介绍了关系型容器去模板化的一个技巧:将operator<比较运算符生成为实体函数,并在容器中使用函数指针来比较元素。如果把这个技巧更广泛的应用,就可以把元素类型的所有操作全变成函数指针调用。STL中对于元素的主要操作有:构造函数、copy构造函数、析构函数、operator=、operator<、operator==。只要在每个容器中保存这几个函数指针,就可以将所有内部操作全部去模板化。

下面是一个简单示例:

-cpp代码

001
typedef
void
(*_DataUnaryOp)(
void
*
__data);
002
typedef
void
(*_DataBinaryOp)(
void
*
__left,
const
void
*
__right);
003
typedef
bool
(*_DataBinaryPredicate)(
const
void
*
__left,
const
void
*
__right);
004
005
template
<
typename
_Tp>
006
void
_construct_fn(
void
*
__data)
007
{
008
new
(__data)
_Tp();
//
new的使用无法避免
009
}
010
011
template
<
typename
_Tp>
012
void
_destruct_fn(
void
*
__data)
013
{
014
static_cast
<_Tp*>(__data)->~_Tp();
015
}
016
017
template
<
typename
_Tp>
018
void
_copy_construct_fn(
void
*
__new_obj,
const
void
*
__old_obj)
019
{
020
new
(__new_obj)
_Tp(*(
static_cast
<
const
_Tp*>(__old_obj)));
//
new的使用无法避免
021
}
022
023
template
<
typename
_Tp>
024
void
_assign_fn(
void
*
__new_obj,
const
void
*
__old_obj)
025
{
026
*
static_cast
<_Tp*>(__new_obj)
= *
static_cast
<
const
_Tp*>(__old_obj);
027
}
028
029
template
<
typename
_Tp>
030
bool
_less_fn(
const
void
*
__left,
const
void
*
__right)
031
{
032
return
*
static_cast
<
const
_Tp*>(__left)
< *
static_cast
<
const
_Tp*>(__right);
033
}
034
035
template
<
typename
_Tp>
036
bool
_equal_fn(
const
void
*
__left,
const
void
*
__right)
037
{
038
return
*
static_cast
<
const
_Tp*>(__left)
== *
static_cast
<
const
_Tp*>(__right);
039
}
040
041
class
_TestVectorBase
042
{
043
_DataUnaryOp
__M_construct_fn;
044
_DataUnaryOp
__M_destruct_fn;
045
_DataBinaryOp
__M_copy_construct_fn;
046
_DataBinaryOp
__M_assign_fn;
047
_DataBinaryPredicate
__M_less_fn;
048
_DataBinaryPredicate
__M_equal_fn;
049
050
void
*
__M_storage;
051
052
protected
:
053
_TestVectorBase(_DataUnaryOp
__construct_fn, _DataUnaryOp __destruct_fn,
054
_DataBinaryOp
__copy_construct_fn, _DataBinaryOp __assign_fn,
055
_DataBinaryPredicate
__less_fn, _DataBinaryPredicate __M_equal_fn)
056
:
__M_construct_fn(__construct_fn), __M_destruct_fn(__destruct_fn),
057
  
__M_copy_construct_fn(__copy_construct_fn),
__M_assign_fn(__assign_fn),
058
  
__M_less_fn(__less_fn),
__M_equal_fn(__M_equal_fn)
059
{}
060
061
void
_M_push_back(
void
*
__element)
062
{
063
__M_storage
=
malloc
(100);
064
__M_copy_construct_fn(__M_storage,
__element);
//
通过函数指针调用了copy构造函数,等同于new(__M_storage) _Tp(__element)
065
}
066
};
067
068
template
<
typename
_Tp>
069
class
_TestVector
:
public
_TestVectorBase
070
{
071
public
:
072
_TestVector()
: _TestVectorBase(_construct_fn<_Tp>, _destruct_fn<_Tp>, _copy_construct_fn<_Tp>, _assign_fn<_Tp>, _less_fn<_Tp>, _equal_fn<_Tp>){}
073
074
void
push_back(_Tp
__element)
075
{
076
_M_push_back(&__element);
//
不再需要传入元素大小
077
}
078
};
079
080
class
_TestClass
081
{
082
int
x;
083
084
public
:
085
_TestClass(){}
086
_TestClass(
const
_TestClass&
__other) : x(__other.x)
087
{
088
cout
<<
"copy
constructor executed!"
<<
endl;
089
}
090
091
bool
operator
<(
const
_TestClass&
__other)
const
{
return
x
< __other.x; }
092
bool
operator
==(
const
_TestClass&
__other)
const
{
return
x
== __other.x; }
093
};
094
095
int
main(
int
argc,
_TCHAR* argv[])
096
{
097
_TestVector<_TestClass>
a;
098
a.push_back(_TestClass());
099
return
0;
100
}
既然此方法确实可行,为什么我不采用这种技术实现一个全功能的STL版本呢?——先抛开new运算符的使用和函数指针需要占用的空间不谈,我最担心的问题是:运行性能。

一旦像这样把所有元素操作都变成函数指针调用,那么在容器内部代码中就必须大量调用函数指针。且无论对于多么简单的类型都无法跳过这一过程!试想一下,在复制一个vector<char>容器时,对于每一个字节都调用一次函数指针来赋值是件多么傻的事情……即使char类型不需要析构函数,vector<char>容器在析构时也必须挨个对每个元素调用析构函数指针来尝试“析构”——即使这些指针指向的是空函数,函数调用开销也是存在的。

POD_STL项目所做的,是在内部去模板化的前提下,尽最大的努力来维持原有性能。使用memcpy复制元素,并省略掉析构函数调用是我能想到的性能最优的做法了。(其实对于某些操作,比如vector内成员的批量搬移,POD_STL的性能反而比标准STL更高,因为它只执行一次memcpy操作)

尽管如此,我仍然很期待有其他高手实现出全功能的无模板膨胀STL版本!毕竟多一种选择不是坏事,而且说不定它的实测性能并没有那么差。

心得体会

我喜欢C++,其中的各种高级模板特性让我非常着迷。这次对于STL源代码的重写让我更加深入了解了其内部实现,里面很多的模板编码技巧用得赏心悦目,看懂以后有茅塞顿开的感觉。如果你想深入学习C++和STL,我相信参与开发这个项目会是个很好的选择,对于编码能力会有很大提升。

但另一方面,在深入阅读STLport开源代码后,发现它也有很多缺点。既有考虑不周,性能不佳之处,也有功能上的BUG(见《STLport源代码中的一个BUG》)。因此,在学习代码“名作”长处的同时,也不应对其过于迷信。

POD_STL的全部代码已上传到GitHub开源平台,欢迎有兴趣的朋友参与开发和提出点评。

链接:https://github.com/Goalsum/POD_STL
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  STL C++ 模板代码膨胀