为什么大多数 STL 实现中的代码如此复杂?

STL 是 C + + 世界中的一个关键部分,大多数实现都源于 Stepanov 和 Musser 最初的努力。

我的问题是代码的关键性,它是人们为了敬畏和学习的目的而查看编写良好的 C + + 的例子的主要来源之一: 为什么 STL 的各种实现看起来如此令人厌恶——从美学的角度来看,这些例子是如何不编写 C + + 代码的。

下面的代码示例无法通过我工作过的地方的代码审查,原因各不相同,从变量命名、布局、宏和运算符的使用,这些都需要不仅仅是简单的一瞥来弄清楚实际发生了什么。

template<class _BidIt> inline
bool _Next_permutation(_BidIt _First, _BidIt _Last)
{  // permute and test for pure ascending, using operator<
_BidIt _Next = _Last;
if (_First == _Last || _First == --_Next)
return (false);


for (; ; )
{  // find rightmost element smaller than successor
_BidIt _Next1 = _Next;
if (_DEBUG_LT(*--_Next, *_Next1))
{  // swap with rightmost element that's smaller, flip suffix
_BidIt _Mid = _Last;
for (; !_DEBUG_LT(*_Next, *--_Mid); )
;
_STD iter_swap(_Next, _Mid);
_STD reverse(_Next1, _Last);
return (true);
}


if (_Next == _First)
{  // pure descending, flip all
_STD reverse(_First, _Last);
return (false);
}
}
}




_Ty operator()()
{  // return next value
static _Ty _Zero = 0;   // to quiet diagnostics
_Ty _Divisor = (_Ty)_Mx;


_Prev = _Mx ? ((_Ity)_Ax * _Prev + (_Ty)_Cx) % _Divisor
: ((_Ity)_Ax * _Prev + (_Ty)_Cx);
if (_Prev < _Zero)
_Prev += (_Ty)_Mx;
return (_Prev);
}

请注意,我不是批评界面,因为它是非常好的设计和适用性。我所关心的是实现细节的可读性。

以前也提出过类似的问题:

STL 是否有可读的实现

为什么 STL 的实现如此不可读? C + + 是如何在这里得到改进的?

注意: 以上代码取自 MSVC 2010算法和队列头。

9829 次浏览

关于变量名,库实现者必须使用“疯狂”的命名约定,例如名称以下划线开头,后面跟着大写字母,因为这样的名称是为它们保留的。它们不能使用“普通”名称,因为这些名称可能已被用户宏重新定义。

第17.6.3.2节“全球名称”1规定:

某些名称和函数签名总是保留给实现:

  • 包含双下划线或以下划线开头、后跟大写字母的每个名称都保留给实现以供使用。

  • 以下划线开头的每个名称都保留给实现,以便在全局命名空间中作为名称使用。

(请注意,这些规则禁止像 __MY_FILE_H这样的头部防护,我经常看到这种情况。)

变量名称的原因是这是标准库代码,并且它应该使用保留名称作为标头中的实现细节。没有应该打破以下标准库:

#define mid
#include <algorithm>

因此,标准库头不能使用 mid作为变量名,因此使用 _Mid。STL 是不同的-它不是语言规范的一部分,它被定义为“这里有一些头,你可以随意使用它们”

另一方面,如果使用 _Mid作为变量名,那么您的代码或我的代码将是无效的,因为这是一个保留名称——实现允许这样做:

#define _Mid

如果你愿意的话。

布局。他们可能有一个风格指南,他们可能遵循它,或多或少。事实上,它不符合我的样式指南(因此会失败我的代码审查)是没有什么给他们。

很难算出的操作员——对谁来说很难?代码应该为维护它的人编写,GNU/Dinkumware/任何可能不想让人们松懈的标准库谁不能解开 *--_Next一目了然。如果你使用这种表达方式,你会习惯它,如果你不这样做,你会继续发现它很难。

不过我可以告诉你 operator()过载是胡言乱语。【编辑: 我明白了,这是一个线性同余方法,非常通用,如果模是“0”,那就意味着使用算术类型的自然包装。】

我怀疑部分原因是 STL 中的代码被高度优化了。所实现的代码的性能比可读性重要得多。因为它们被广泛使用,所以尽可能快地制造它们是有意义的。

实现各不相同。例如 Libc + + ,对眼睛来说就容易多了。不过还是有点下划线的噪音。正如其他人指出的那样,不幸的是,前面的下划线是必需的。下面是 libc + + 中相同的函数:

template <class _Compare, class _BidirectionalIterator>
bool
__next_permutation(_BidirectionalIterator __first, _BidirectionalIterator __last, _Compare __comp)
{
_BidirectionalIterator __i = __last;
if (__first == __last || __first == --__i)
return false;
while (true)
{
_BidirectionalIterator __ip1 = __i;
if (__comp(*--__i, *__ip1))
{
_BidirectionalIterator __j = __last;
while (!__comp(*__i, *--__j))
;
swap(*__i, *__j);
_STD::reverse(__ip1, __last);
return true;
}
if (__i == __first)
{
_STD::reverse(__first, __last);
return false;
}
}
}

为了补充人们已经说过的内容,您看到的样式是 GNU 样式。丑陋?也许,这只是旁观者的看法。但它是一种严格定义的样式,并且它确实使所有代码看起来相似,而不是难以习惯。