Почему код в большинстве реализаций STL настолько запутан? - PullRequest
63 голосов
/ 15 ноября 2010

STL является критически важной частью мира C ++, большинство реализаций основано на первоначальных усилиях Степанова и Муссера.

Мой вопрос связан с критичностью кода, и он является одним из основных источников, позволяющих людям просматривать примеры хорошо написанного 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 и заголовков очереди.

Ответы [ 6 ]

19 голосов
/ 15 ноября 2010

Нил Баттерворт, который теперь указан как «anon», предоставил полезную ссылку в своем ответе на вопрос SO «Есть ли читаемая реализация STL?» . Цитирую свой ответ там:

Есть книга Стандарт C ++ Библиотека шаблонов, в соавторстве с оригинальные дизайнеры STL Степанов & Ли (вместе с П.Я. Плаугером и Дэвидом Муссер), который описывает возможные реализация, в комплекте с кодом - увидеть http://www.amazon.co.uk/C-Standard-Template-Library/dp/0134376331.

См. Также другие ответы в этой теме.

Во всяком случае, большая часть кода STL (под STL я здесь имею в виду STL-подобное подмножество стандартной библиотеки C ++) является шаблоном кода и поэтому должна быть только для заголовков, и поскольку она используется почти в каждой программе, которую она платит чтобы этот код был как можно короче.

Таким образом, естественный компромисс между краткостью и удобочитаемостью намного дальше в конце краткости шкалы, чем с «нормальным» кодом.

Кроме того, в стандартной библиотеке системно-независимое представление кода приложения связано с базовой системой, используя все виды специфичных для компилятора вещей, от которых вам, как разработчику приложения, лучше всего избегать.

18 голосов
/ 15 ноября 2010

Что касается имен переменных, разработчики библиотеки должны использовать «сумасшедшие» соглашения об именах, такие как имена, начинающиеся с символа подчеркивания, за которым следует заглавная буква, поскольку такие имена зарезервированы для них.Они не могут использовать «нормальные» имена, потому что они могут быть переопределены пользовательским макросом.

Раздел 17.6.3.3.2 «Глобальные имена» Состояния §1:

Некоторые наборыимен и сигнатур функций всегда зарезервированы для реализации:

  • Каждое имя, которое содержит двойное подчеркивание или начинается со знака подчеркивания, за которым следует заглавная буква, резервируется для реализации для любого использования.

  • Каждое имя, начинающееся с подчеркивания, зарезервировано для реализации для использования в качестве имени в глобальном пространстве имен.

(Обратите внимание, что эти правила запрещают защиту заголовков, такую ​​как __MY_FILE_H, которую я видел довольно часто.

11 голосов
/ 15 ноября 2010

Имена переменных по той причине, что это стандартный код библиотеки, и он должен использовать зарезервированные имена для деталей реализации в заголовках. Следующее должно не сломать стандартные библиотеки:

#define mid
#include <algorithm>

Таким образом, заголовки стандартной библиотеки не могут использовать mid в качестве имени переменной, следовательно, _Mid. STL был другим - он не был частью спецификации языка, он был определен как «вот некоторые заголовки, используйте их как хотите»

Ваш код или мой, с другой стороны, был бы недействительным, если бы он использовал _Mid в качестве имени переменной, поскольку это зарезервированное имя - реализация может делать:

#define _Mid

если хочется.

Макет - мех. У них, вероятно, есть руководство по стилю, они, вероятно, следуют ему, более или менее. Тот факт, что он не соответствует моему руководству по стилю (и, следовательно, может провалиться при проверке кода), для них ничего не значит.

Операторы, с которыми сложно работать - кому сложно? Код должен быть написан для людей, которые его поддерживают, и для GNU / Dinkumware / тех, кто, вероятно, не хочет выпускать людей из стандартных библиотек, которые не могут сразу разгадать *--_Next. Если вы используете такое выражение, вы привыкнете к нему, а если не будете, то продолжите находить его сложным.

Я дам вам, однако, что перегрузка operator() - это бред. [Править: Я понял, это линейный конгруэнтный генератор, выполненный очень обобщенно, и если модуль равен «0», это означает, что нужно просто использовать естественный перенос арифметического типа.]

4 голосов
/ 15 ноября 2010

Реализации варьируются.Например, 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;
        }
    }
}
2 голосов
/ 15 ноября 2010

Я подозреваю, что отчасти причина в том, что код в STL сильно оптимизирован. Реализуемый код имеет гораздо более высокую производительность, чем удобочитаемость. Поскольку они так широко используются, имеет смысл сделать их как можно быстрее.

0 голосов
/ 15 ноября 2010

Чтобы добавить то, что люди уже сказали, стиль, который вы видите, это стиль GNU. Гадкий? Возможно, это в глазах смотрящего. Но это строго определенный стиль, и он делает весь код похожим, а не устойчивым к привыканию.

...