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

时间:2010-11-14 22:51:26

标签: c++ stl readability

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);
   }

请注意我并不批评界面,因为它设计得很好并且适用。我关心的是实现细节的可读性。

之前提出过类似的问题:

Is there a readable implementation of the STL

Why STL implementation is so unreadable? How C++ could have been improved here?

注意:上面的代码取自MSVC 2010算法和队列标题。

6 个答案:

答案 0 :(得分:19)

Neil Butterworth,现在被列为“anon”,在回答SO问题"Is there a readable implementation of the STL?"时提供了一个有用的链接。在那里引用他的答案:

  

有一本书C ++标准   模板库,由人工合着   原STL设计师Stepanov&amp;背风处   (与P.J. Plauger和David一起   Musser),描述了一种可能   实施,完成代码 -   看到   http://www.amazon.co.uk/C-Standard-Template-Library/dp/0134376331

另见该主题中的其他答案。

无论如何,大多数STL代码(由STL我在这里指的是C ++标准库的类似STL的子集)是模板代码,因此必须是仅标题,并且因为它几乎在每个程序中都使用它支付让代码尽可能短。

因此,简洁性和可读性之间的自然权衡点远远超过量表的简洁性,而不是“普通”代码。

此外,标准库是应用程序代码的系统无关视图连接到底层系统的地方,利用您作为应用程序开发人员应该最好远离的各种特定于编译器的东西。

答案 1 :(得分:17)

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

第17.6.3.3.2节“全球名称”§1陈述:

  

某些名称和功能签名集始终保留给实现:

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

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

  •   

(请注意,这些规则禁止我经常看到__MY_FILE_H这样的标题保护。)

答案 2 :(得分:11)

变量名称,因为这是标准库代码,它应该在头文件中使用保留名称来实现详细信息。以下应该打破标准库:

#define mid
#include <algorithm>

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

另一方面,如果代码或我的代码使用_Mid作为变量名称,那么它将是无效的,因为这是一个保留名称 - 允许执行:

#define _Mid

如果感觉就好。

布局 - 嗯。他们可能有一个风格指南,他们可能会或多或少地遵循它。它与我的风格指南不符(因此我的代码审查失败)这一事实对他们来说并不算什么。

难以锻炼的操作员 - 难以为谁服务?代码应该是为维护代码的人编写的,而GNU / Dinkumware /他们可能不希望让人们忽略那些无法一目了然地解开*--_Next的标准库。如果你使用那种表达方式,你会习惯它,如果你不习惯,你会继续努力找到它。

但是,我会告诉你,operator()超载是胡言乱语。 [编辑:我明白了,它是一个线性同余生成器,非常通用,如果模数为“0”,则意味着只使用算术类型的自然环绕。]

答案 3 :(得分:4)

实施方式各不相同。例如,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;
        }
    }
}

答案 4 :(得分:2)

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

答案 5 :(得分:0)

要添加人们已经说过的内容,你看到的风格是GNU风格。丑陋?也许,那是在旁观者的眼中。但它是一种严格定义的风格,它确实使所有代码看起来都相似,而不是耐心习惯。