Re[12]: Проблемы STL-контейнеров
От: Cyberax Марс  
Дата: 08.09.06 15:50
Оценка: +1
Kluev wrote:
> kan>Делается просто — несколько типов итераторов. Классический пример —
> begin-end/rbegin-rend. Для дерева можно 2 типа
> kan>сделать — обход в ширину и обход в глубину.
> Не юзабельно. Т.к. рекурсия в данном случае проще и лучше.
Юзабельно. Это фактически просто развернутая рекурсия.

> kan>Т.е. функции, возвращающие итераторы могут быть даже с аргументами,

> например, задать по какой "кривой" обойти
> kan>многомерный массив.
> Опять же неюзабельно. Во превых такой итератор прийдется писать, во
> вторых зачем? Если нужен какой-то путь в массиве, то отдельный массив с
> индексами то что нужно. Просто и в написании и в отладке.
Зачем писать? Просто создается нужный функтор.

Из личного опыта — кроме как итераторами трехмерный разреженный массив
обходить все равно удобно не получится.

>> > 1) гораздо проще в реализации чем итератор

>> > 2) программы с его использованием получаются более элегантными
> kan>Требует задания функтора. Всегда. А это не всегда элегантно.
> Не всегда. в контейнере просто заводятся функуци с визитором и без.
Визитор сам по себе неудобен. Так как в функции-визиторе нельзя получить
информацию о следующем или предидущем члене.
Posted via RSDN NNTP Server 2.0
Sapienti sat!
Re[5]: Проблемы STL-контейнеров
От: Roman Odaisky Украина  
Дата: 08.09.06 15:58
Оценка:
Здравствуйте, kan, Вы писали:

>> for(i = 0, j = vec.size()-1; i < j; i++)


>> Со знаковыми типами вот такой код будет нормально работать, а с

>> бесзнаковыми i,j при vec.size()==0 будут грабли.
kan>А зачем такой ужас писать? Чем банальный
kan>for(size_t i = 0; i + 1 < vec.size(); i++)
kan>не устраивает?

Здесь ты неправ. Вот пару дней назад на топкодере была задача, связанная с выпуклостью функции, заданной массивом значений. Написал на листочке формулу, перевел в Ц++. Вот что вышло:
if( (z - y) * (*y - *x) < (*z - *y) * (y - x) ) { ... }
x, y, z — итераторы.

С математической точки зрения всё правильно. Но если вдруг value_type(x) = unsigned, получим сюрпрайз.

Или другая задача. Есть много чисел (могут быть и <0), есть число M. Для каждого значения из [0, M) надо знать, сколько чисел из исходного набора имеют такой остаток при делении на M.
++count_vec[очередноёзначениё % count_vec.size()];
??

— Отгадай, что такое: черное, волосатое, на дне моря лежит?
— ???
— Подводная лодка.
— ??????
— Бородой накрылась.*

* В иных версиях лодка накрывается другими созвучными объектами.
До последнего не верил в пирамиду Лебедева.
Re[12]: Проблемы STL-контейнеров
От: kan Великобритания  
Дата: 08.09.06 17:39
Оценка:
Kluev wrote:

>> > ЮБ>Ты себе представляешь stl без итераторов?

>> > Цель итераторов — сделать перебор элементов в разных контейнерах
>> > одинаковым. Это больная идея т.к. контейнеры должны перебиратся наиболее
>> > нативным для них способом. Как например должен выглядеть итератор для
>> > многомерных массивов, кольцевых списков и деревьев? Многие контейнеры в
> kan>Делается просто — несколько типов итераторов. Классический пример —
> begin-end/rbegin-rend. Для дерева можно 2 типа
> kan>сделать — обход в ширину и обход в глубину.
> Не юзабельно. Т.к. рекурсия в данном случае проще и лучше.
Причём тут рекурсия? Рекурсия — не алгоритм, а метод (способ) реализации алгоритма. "обход в ширину и обход в глубину" —
два разных алгоритма обхода дерева, которые могут быть (а могут и не быть) реализованы с помощью рекурсии.

> kan>Т.е. функции, возвращающие итераторы могут быть даже с аргументами,

> например, задать по какой "кривой" обойти
> kan>многомерный массив.
> Опять же неюзабельно. Во превых такой итератор прийдется писать, во
> вторых зачем? Если нужен какой-то путь в массиве, то отдельный массив с
> индексами то что нужно. Просто и в написании и в отладке.
В смысле обход элементов многомерного массива. Например, можно обходить вначале 1-е, потом 2-е, потом 3-е измерение. А
можно вначале 3-е, потом 1-е, потом 2-е. И т.д. Т.е. куча разных вариантов обхода элементов массива. Можно сделать
метод, который будет выдавать "обходчик массива" (итератор, если по-русски) в зависимости от того, какой способ мы хотим
в данный момент использовать.

>> > 1) гораздо проще в реализации чем итератор

>> > 2) программы с его использованием получаются более элегантными
> kan>Требует задания функтора. Всегда. А это не всегда элегантно.
> Не всегда. в контейнере просто заводятся функуци с визитором и без.
Не понял.

> kan>Не позволяет иметь несколько итераторов, позволяющих обходить

> контейнер в разном порядке одновременно.
> Для этого есть обычные циклы которые как раз для этих вещей и
> предназначены. Чтобы ходить как хочешь и куда хочешь.
Объясни, что является "обычным циклом" при обходе vector? а deque? а list? И почему я должен переписывать этот цикл,
если я вдруг поменял list на vector?

>> > Третье преимущество в том что внутри container.clear(clear_func) пербор

>> > идет способом родным для контейнера, что существенно упрощает отладку.
> kan>Какой способ является родным для дерева? Да даже для того же
> массива? Почему от начала к концу, а не наоборот? Или с
> kan>середины к краям?
> Для дерева родной способ рекурсия, для массива и списка итерация.
Бред. Что такое рекурсия для дерева? Кто мне помешает обойти дерево итерацией, а массив рекурсией?

> Иетартор всегда итерация, а значит написать итератор например для дерева

> может оказатся весьма непростой задачей. for_each не должен зависить от
> порядка элементов, а если требуются какие-то особые случаи то надо
> делать обычный цикл.
Может зависеть, может не зависеть. Да и не только ради for_each задумывалось всё это дело.

>> > Так же итераторы в стиле stl совершенно не подходят для перебора

>> > кольцевых контейнеров.
>> > Т.к. в силу замкнутости их требуется итерировать не в диапазоне [begin,
>> > end), а в диапазоне [begin, last]
> kan>[begin, last] никак не позволяет задавать пустые интервалы, и никак
> это не обойти, и это является наиболее важным. Тем
> kan>более не надо делать замкнутый итератор единственно доступным для
> контейнера. Пусть это будет обычный std::list, просто
> kan>для него создать ещё один тип итератора, который будет замыкаться.
> Это как? На каждой итерации проверять не в конце ли мы и если в конце то
> переходить на первый элемент? Значит ради идей уже пожертвовали
Как реализуешь, так и будет. Итератор по этому поводу ничего не требует.
Контейнер может возвращать кольцевой итератор и пару итераторов, которые будут [first,last).

> удобством и отладкой, а теперь вот еще и на производительность плевать.

> Не самая лучшая идея. Более того такое решение все равно будет более
> неудобным чем специализированный контейнер.
Библиотека stl проектировалась именно так, чтобы можно было делать эффективные имплементации... так что непонятно о чём ты?
Posted via RSDN NNTP Server 2.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: Точки над i
От: Smal Россия  
Дата: 08.09.06 17:52
Оценка: 14 (8) +9 -1 :)
Здравствуйте, all.

Долго пытался не влезать в эту дискуссию, но все же попытаюсь изложить собственное мнение.
(Ибо пятница, вечер и лениво делать что-то умное. К тому же ребилд %)).

Читая эту уже порядком затянувшуюся ветку, все больше и больше убеждаюсь, что
все "Минусы STL" и "Проблемы STL контейнеров" кроются в закостенелости мышления.
Большинство упреков (есть, конечно, и минусы, без этого нельзя) появляются
вследствие не(до)понимания каких-то концепций.

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

Это также объясняется тем, что некоторые воспринимают C++ только как синтаксическое
расширение C, т.е. не принимают (не понимают или даже не пытаются понять) тех парадигм,
которые делают С++ высокоуровневым языком.

Да, можно успешно писать на С++ не используя стандартную библиотеку,
ровно как писать на Java и использовать собственные контейнеры и алгоритмы.
Но осмысленно ли это?

Те, кто говорят, что «STL плоха, ибо я ее не использую», скорей всего просто не разобрались STL.

Те, кто говорят, что «STL не юзабельная», просто не видели больших проектов, построенных с использованием STL.
(В продукте над которым я работаю около 200 проектов. Все построены на COM + STL. Как это не удивительно, но
юзать MFC никому даже в голову не пришло %)).

Те же, кто говорят, что STL бедна, ибо не реализует B-деревья, топологическую сортировку,
фибоначиву кучу, алгоритмы Дейкстры, Форда-Фалкерсона, Джонсона…, не решает задачи
о рюкзаке, коммивояжере, максимальном потоке…, не предоставляет средства для работы с сокетами,
USB, сотовыми телефонами, Wi-Fi и т.д. требуют от стандартной библиотеки слишком много.

Некоторые думают, что STL жутко тормозная и экономят каждую инструкцию в тех местах, где это никому
не нужно (пресловутая преждевременная оптимизация). К примеру, в GUI.
В действительности, даже такие структуры как std::map<string> достаточно быстры,
что позволяют писать высокоточные профайлеры.

Ну, а те, кого не интересует мнения классиков, обречены вечно наступать на свои же грабли.
Ибо, как известно, дурак учится на своих ошибках, а умный на чужих %).

К сожалению, такой порядок дел не изменить в силу все той же закостенелости мышления и нежелании что-либо менять.
Поэтому, ИМХО, данная ветка уже давно перешла в ранг холиворов вроде C vs C++.

Остается только надеяться, что такое положение дел положительно скажется на развитии стандартной библиотеки.

--
С уважением, Александр
С уважением, Александр
Re: минусы STL
От: Аноним  
Дата: 08.09.06 18:34
Оценка: :))
Здравствуйте, gid_vvp, Вы писали:

_>Hi All


_>перечислите, на ваш взгляд, основные минусы STL

_>принимаются только ответы с аргументацией

Минусы STL — это плюсы Си.

P.S. Вдумайтесь — этот ответ гениален.
Re[3]: Проблемы STL-контейнеров
От: Пётр Седов Россия  
Дата: 14.09.06 23:16
Оценка:
Здравствуйте, Alex_Avr, Вы писали:
A_A>Здравствуйте, Аноним, Вы писали:

А>>3. Нет понятия 'NULL-итератор'. В качестве итератора, "ссылающегося в никуда", обычно используют Container.end(). Но вместо кода:

А>>
А>>if (m_itSelectedItem != m_pServer->ItemsEnd())
А>>{
А>>  m_pServer->ReleaseItem(m_itSelectedItem);
А>>  m_itSelectedItem = m_pServer->ItemsEnd();
А>>}
А>>

А>>было бы гораздо удобнее писать:
А>>
А>>if (m_itSelectedItem != NULL) // NULL-итератор
А>>{
А>>  m_pServer->ReleaseItem(m_itSelectedItem);
А>>  m_itSelectedItem = NULL; // NULL-итератор
А>>}
А>>

А>>Особенно, если такого кода много. Вариант с NULL-итератором лучше по всем показателям:
А>> A_A>Как-то не было проблем с чтением.
'm_pServer->ItemsEnd()' — три слова.
'NULL' — одно слово.
Я вариант с 'NULL' читаю быстрее. Когда вижу слово 'NULL', в голове сразу же появляется мысль: "нет объекта". Нужно чуть больше времени, чтобы понять, что такое 'm_pServer->ItemsEnd()'.

А>>
A_A>В современных IDE разницы практически нет.
В современных IDE программисты часто ускоряют написание ' != NULL'. А вот ускорить написание ' != m_pServer->ItemsEnd()' сложнее из-за большого количества вариантов.

А>>
А>> A_A>Не факт, компилятор может встроить тело end () в место вызова.
Я проверил в MSVC6 Release. Контейнер — list. Вариант с end-итератором:
      void Client::ReleaseRecentItem()
      {

004014F0   push        esi
004014F1   mov         esi,ecx

        if (m_itRecentItem != m_pServer->ItemsEnd())

004014F3   mov         ecx,dword ptr [esi]
004014F5   mov         eax,dword ptr [esi+4]
004014F8   cmp         eax,dword ptr [ecx+4]
004014FB   je          Client::ReleaseRecentItem+1Dh (0040150d)

        {
          m_pServer->ReleaseItem(m_itRecentItem);

004014FD   mov         edx,eax
004014FF   push        edx
00401500   call        Server::ReleaseItem (004040c0)

          m_itRecentItem = m_pServer->ItemsEnd();

00401505   mov         eax,dword ptr [esi]
00401507   mov         ecx,dword ptr [eax+4]
0040150A   mov         dword ptr [esi+4],ecx
0040150D   pop         esi

        }
      }

0040150E   ret

--- No source file  ------------------------------------------------------------

0040150F   nop

Компилятор действительно встроил оба вызова Server::ItemsEnd (возвращает 'm_Items.end()'). Размер функции: 0x0040150F — 0x004014F0 = 31 байт. Вариант с NULL-итератором:
      void Client::ReleaseRecentItem()
      {

004014F0   push        esi
004014F1   mov         esi,ecx

        if (m_itRecentItem != NULL)

004014F3   mov         eax,dword ptr [esi+4]
004014F6   test        eax,eax
004014F8   je          Client::ReleaseRecentItem+19h (00401509)

        {
          m_pServer->ReleaseItem(m_itRecentItem);

004014FA   mov         ecx,dword ptr [esi]
004014FC   push        eax
004014FD   call        Server::ReleaseItem (004040c0)

          m_itRecentItem = NULL;

00401502   mov         dword ptr [esi+4],0
00401509   pop         esi

        }
      }

0040150A   ret

--- No source file  ------------------------------------------------------------

0040150B   nop

Размер функции: 0x0040150B — 0x004014F0 = 27 байт. Разница действительно мизерная (4 байта). Время работы, скорее всего, тоже почти одинаково (не мерял). Но в данном случае размер ассемблерного кода и скорость работы на втором плане. В первую очередь, NULL-итераторы дают:

A_A>Итераторы могут быть классами, а не простыми указателями (как в STLPort в отладочном билде), в этом случае такой поход работать не будет.

Обычно итераторы — это обёртки над указателями. Поэтому легко добавить поддержку NULL-итераторов. Например, для list-а:
template<typename TElem, ...>
class list
{
  ...
  struct Node
  {
    Node* pPrev;
    Node* pNext;
    TElem Elem;
  };
  ...
  class iterator
  {
    Node* m_pNode;

  public:
    iterator()
    {
    }

    // NULL-итератор
    iterator(int NullTag)
    {
      assert(NullTag == 0);
      m_pNode = NULL;
    }

    TElem& operator*() const
    {
      return m_pNode->Elem;
    }

    TElem* operator->() const
    {
      return &m_pNode->Elem;
    }

    iterator& operator--()
    {
      m_pNode = m_pNode->pPrev;
      return *this;
    }

    iterator& operator++()
    {
      m_pNode = m_pNode->pNext;
      return *this;
    }

    iterator operator--(int PostfixTag)
    {
      iterator it = *this;
      operator--();
      return it;
    }

    iterator operator++(int PostfixTag)
    {
      iterator it = *this;
      operator++();
      return it;
    }

    friend bool operator==(iterator it1, iterator it2)
    {
      return it1.m_pNode == it2.m_pNode;
    }

    friend bool operator!=(iterator it1, iterator it2)
    {
      return it1.m_pNode != it2.m_pNode;
    }
  };
  ...
};

Такой подход работает уже 8 лет, так как MSVC6 вышел в 98-ом году:
list<int>::iterator it1 = NULL; // итератор list-а - класс
assert(it1 == NULL);
assert(!(it1 != NULL));

map<int, int>::iterator it2 = NULL; // итератор map-а - класс
assert(it2 == NULL);
assert(!(it2 != NULL));


А>>5. Нельзя получить итератор по указателю на элемент контейнера за константное время.

A_A><код поскипан>
А>>Желательно, чтобы код, выделенный жирным, работал за константное время.
A_A>А кто сказал, что это возможно для любых итераторов и любых контейнеров?
Никто. Но для некоторых возможно:
Можно поступить так же, как с list::size: стандарт рекомендует, чтобы iterator_from_pointer работал за константное время, но реализация может работать за время O(N) (N — количество элементов в контейнере).

А>>9. list

А>>9.1. list спроектирован так, что либо size за константное время, либо splice (вариант с 4-мя параметрами) за константное время (при условии, что allocator-ы равны). Стандарт рекомендует ("should"), чтобы list::size работал за константное время, но выбор оставлен на усмотрение реализации STL. Соответственно, есть "странные" реализации STL (например, SGI STL, раздел "Why is list<>::size() linear time?"), в которых size вычисляется, а не хранится. То есть реализация size-а пробегает по всему списку и таким образом узнаёт количество элементов.
А>>В MFC CList::GetCount работает за константное время:
А>>

А>>

А>>template<class TYPE, class ARG_TYPE>
А>>AFX_INLINE int CList<TYPE, ARG_TYPE>::GetCount() const
А>>  { return m_nCount; }
А>>

А>>О проблеме писали здесь
Автор: Sergeem
Дата: 26.05.03
, здесь
Автор: Artour A. Bakiev
Дата: 18.01.04
.

A_A>То, что в одной из реализаций STL list::size () имеет линейную сложность еще ни очем не говорит.
Это говорит о том, что стоит осторожно обращаться с list::size. Например, выносить за тело цикла, если список не меняется в ходе цикла. Если меняется, то можно запросить list::size один раз до цикла, а в ходе цикла вручную отслеживать длину списка. Правильность ручной длины списка можно проверять assert-ом.
Кстати, STLport 4.5.3 тоже "странная" (файл _list.h):

template <...>
class list ... {
  ...
  size_type size() const {
    size_type __result = distance(begin(), end());
    return __result;
  }
  ...
};


A_A>Да и есть ли в CList аналог list::splice ()?

Нет. В MFC 4.2 каждый CList имеет личный менеджер памяти (pool). Поэтому нельзя дёшево перетащить элементы из одного списка в другой. Можно только дорого:

template<...>
void CList<...>::AddHead(CList* pNewList)
{
  ...
  // add a list of same elements to head (maintain order)
  POSITION pos = pNewList->GetTailPosition();
  while (pos != NULL)
    AddHead(pNewList->GetPrev(pos));
}

template<...>
void CList<...>::AddTail(CList* pNewList)
{
  ...
  // add a list of same elements
  POSITION pos = pNewList->GetHeadPosition();
  while (pos != NULL)
    AddTail(pNewList->GetNext(pos));
}


А>>9.2. Неизвестен порядок уничтожения элементов в деструкторе list-а. Специализированный менеджер памяти, работающий как stack, может требовать, чтобы блоки памяти освобождались в обратном порядке. Я проверил две реализации STL: MSVC6 STL и Rogue Wave STL 2.1.1 (поставляется вместе с BCB5). В обеих ~list уничтожает элементы в прямом порядке. То есть если я наполняю list push_back-ами, то stack-овый менеджер памяти использовать нельзя.

A_A>А когда это нужно?
Например, есть кратковременный список, живущий в блоке кода. Если функция вызывается часто (например, при resize-е окна), то желательно оптимизировать распределение памяти. Можно использовать alloca, но это опасно (риск получить stack overflow). Лучше использовать stack-овый менеджер памяти. Это редкий случай, когда оптимизация почти не усложняет код. Вместо:
typedef list<Item> ItemList;
ItemList Items;

достаточно было бы написать:
typedef list<Item, TempAllocator<Item> > ItemList;
ItemList Items;


A_A>На мой вгляд, закладываться на особенности внутренней реализации контейнера не есть правильно.

Теоретически, ~list может уничтожать элементы в случайном порядке:
template<typename TElem, ...>
list<TElem, ...>::~list()
{
  while (!empty())
  {
    int Index = rand() % size();
    iterator it = begin();
    advance(it, Index);
    erase(it);
  }
  // что-нибудь ещё
  ...
}

Фактически, выбор маленький: уничтожать элементы в прямом порядке или в обратном. Гарантия на этот счёт могла бы помочь при оптимизации.

А>>9.3. push_front и push_back не возвращают итератор на ново-вставленный элемент. Мелочь, а неудобно. В будущем это вполне можно исправить, старый код менять не придётся. Пока, можно сделать обёртку над list-ом:

А>>
А>>template<typename TElem>
А>>class MyList : public list<TElem>
А>>{
А>>public:
А>>  typename iterator push_front(const TElem& e)
А>>  {
А>>    list<TElem>::push_front(e);
А>>    return begin();
А>>  }

А>>  typename iterator push_back(const TElem& e)
А>>  {
А>>    list<TElem>::push_back(e);
А>>    typename iterator it = end();
А>>    return --it;
А>>  }
А>>};
А>>

A_A>Гм, а почему нельзя просто использовать list::front () и list::back ()?
front/back возвращают ссылку на элемент. Иногда нужно добавить элемент в конец списка и получить итератор на ново-вставленный элемент. Например, здесь
Автор: Пётр Седов
Дата: 07.09.06
я писал:

class Item
{
  ...
  explicit Item(const string& Name);
};

class ItemManager
{
public:
  typedef list<Item>::iterator ItemHandle; // ...
  ItemHandle AddItem(const string& Name); // ...
  ...
  list<Item> m_Items;
};

ItemManager::ItemHandle ItemManager::AddItem(const string& Name)
{
  // push_back не возвращает итератор
  return m_Items.insert(m_Items.end(), Item(Name));
}


A_A>Мне кажется, что операции получения итераторов на элементы и вставка разделены не просто так,

Разделены? list::insert, вставляющий один элемент, возвращает итератор на ново-вставленный элемент. Чем push_front/push_back хуже?

A_A>а для того, чтобы можно было гарантировать предсказуемое состояние

A_A>программы в случае, если итератор является классом и при его копировании
A_A>может вылететь исключение:
Итератор list-а — это всегда класс. Но это всего лишь тонкая обёртка над указателем на node.

A_A>

A_A>class A { int n; };

A_A>list<A> list_of_A;
A_A>list<A>::iterator it = list_of_A.push_back (A ()); // если здесь вылетело исключение о нехватки памяти, как
A_A>                                                   // определить произошло это при вставке элемента в контейнер 
A_A>                                                   // или при копировании итератора? И в каком состоянии
A_A>                                                   // теперь находится list_of_A, был ли в него добавлен
A_A>                                                   // элемент?

A_A>

Вряд ли конструктор копирования итератора бросает исключение о нехватке памяти. Зачем итератору запрашивать память? А вот allocator::allocate может бросить исключение о нехватке памяти. В этом случае состояние list_of_A не изменится (список останется пустым).
Обычно итераторы копируются дёшево и без исключений. Если методы итератора и бросают исключения, то это скорее что-нибудь вроде logic_error (а не bad_alloc). Например, усердная реализация STL может ловить сравнение итераторов от разных list-ов (это логическая ошибка).

А>>10. string

А>>10.1. Если программа много работает с текстом, то string (или его аналог) — ходовой тип. При этом желательно, чтобы неявное копирование string-а было дешёвым. Для этого реализация string-а может использовать разделяемый (между несколькими string-ами) буфер с подсчётом ссылок.
A_A>Сейчас , на сколько я знаю, отходят от использования COW при реализации std::string из-за проблем с многопоточностью,
Многие программы однопоточны. Поэтому хорошо бы иметь выбор:
// заголовочный файл <string>
#ifdef _MT // = multi thread
// многопоточная реализация basic_string-а (не считает ссылки)
#else
// однопоточная реализация basic_string-а (считает ссылки)
#endif

Я не люблю, когда такой выбор сделан за меня.

A_A>к тому же это только одна из возможных реализаций.

По большому счёту все реализации string-а делятся на две группы:

A_A>Все-таки как-то странно подгонять интерфейс под реализацию.

В том то и дело, что интерфейс string-а подогнан под реализацию в vector-стиле (без подсчёта ссылок). Есть "плохие" методы, возвращающие не-const ссылку на буфер (например, не-const operator[]). Если реализация считает ссылки, то внешняя не-const ссылка на буфер сильно ограничивает свободу string-а, он вынужден уважать эту ссылку до realloc-а или до деструктора. "Плохой" метод делает copy-on-write (возможно, холостой) и жёстко привязывает буфер к одному единственному string-у. Привязанный буфер не может разделяться между несколькими string-ами. При этом реализация деградирует до vector-стиля и неявное копирование дорожает. К сожалению, интерфейс string-а недостаточно отделён от реализации, он ставит палки в колёса реализации, считающей ссылки.
У MFC-шного CString-а тоже есть методы, возвращающие не-const ссылку на буфер. Но. Во-первых, очень тяжело случайно вызвать методы GetBuffer, GetBufferSetLength и LockBuffer (в отличие от не-const string::operator[]). Во-вторых, есть парные методы (ReleaseBuffer и UnlockBuffer), аннулирующие ранее выданный указатель. Такой подход более дружелюбен к подсчёту ссылок (в MFC 4.2 CString считает ссылки).

A_A>С другой стороны, может было бы лучше как в Java реализовать отдельно классы для неизменяемых строк и строк с возможностью изменения.

Конечно! Разумно разделить класс string на два: string2 и string2_builder, которому и передать всё изобилие методов append, insert, erase и replace; можно ещё добавить prepend. У string2 только базовые способы наполнения: конструкторы, assign и прямой доступ к буферу на запись.

А>>10.2. Нет завершающего '\0'. Вот такой код:

А>>
А>>string Text = "abc";
А>>assert(Text[3] == '\0');
А>>

А>>содержит логическую ошибку, так как '3' является ошибочным индексом для string-а с length = 3. Усердная реализация STL, проверяющая индекс, обнаружит это и сообщит об ошибке (с помощью assert-а или исключения). Если реализация STL беспечная (не проверяет индекс), то фактически код будет работать, так как '\0' в конец обычно ставят (чтобы иметь быструю реализацию метода c_str).
A_A>Используйте правильную STL
A_A>Вообще-то не понял, в чем проблема. Насколько я знаю, в string обычно хранится именно NULL terminating строка.
A_A>Где это не так?
Это везде так, но официально доступ к завершающему '\0' запрещён. Усердная реализация operator[], проверяющая индекс, воспримет доступ к завершающему '\0' как ошибку. Это повлечёт сообщение от assert-а или исключение (вроде out_of_range).

А>>Иногда завершающий '\0' удобен при parsing-е.

A_A>Парсинг это частная задача и подгонять std::string под вашу конкретную его реализацию несколько станно.
При проектировании строкового класса очень разумно учитывать потребности parsing-а. Подгонять ничего не надо: достаточно официально разрешить доступ к завершающему '\0'.

A_A>Целесообразнее, IMHO, парсинг проводить с помощью алгоритмов, регулярных выражений или конечных автоматов.

Или с помощью готовых parser-ов (например, XML-parser). Можно использовать генераторы parser-ов (например, YACC, Lex). Но иногда это стрельба из пушки по воробьям. В простых случаях (как IsValidMethodName) лучше не усложнять код.

А>>В будущем доступ к завершающему '\0' может быть разрешён.

A_A>Ага, что бы пользователь класса сам мог этот нуль затереть и получить AV или SEGFAULT при вызове std::string::find например
Последовательность char-ов string-а может содержать '\0'. Поэтому, в отличие от strchr, string::find не использует завершающий '\0' как барьер, а честно проверяет граничное условие. Затерев завершающий '\0', программист повредит только себе: c_str будет возвращать указатель на буфер, не завершающийся '\0'.
В идеале, хорошо бы чётко разделить чтение и запись:
template<typename TChar, ...>
class basic_string
{
  ...
  // можно читать завершающий '\0'
  TChar operator[](size_type Index) const
  {
    assert(Index <= length());
    ...
  }

  // нельзя писать в завершающий '\0'
  void set_at(size_type Index, TChar c)
  {
    assert(Index < length());
    ...
  }
  ...
};


А>>10.3. Нет прямого доступа к буферу на запись.

A_A>Это позволяет string-у самому управляться со своими данными, что предотвращает проблемы некорректного с ними отношения со стороны пользователя.
Прямой доступ к буферу на запись — низкоуровневая возможность. Она позволяет быстро наполнить string без промежуточного буфера, в том числе с помощью C-style API. Эта возможность нужна в первую очередь создателям высокоуровневых библиотек для эффективной реализации. Новички таким не занимаются, они используют эти самые высокоуровневые библиотеки.
А защищаться от ошибочного использования надо не урезанием функциональности, а отладочными проверками.

А>>
А>>string Text;
А>>int Len = ::GetWindowTextLength(hEditControl);
А>>if (Len != 0)
А>>{
А>>  Text.resize(Len);
А>>  ::GetWindowText(hEditControl, &Text[0], Len + 1);
А>>}
А>>

A_A>Ну да, а если пользователь забыл вызвать resize () или указал недостаточный размер?
Отсутствие прямого доступа к буферу на запись не защищает от такой ошибки. С промежуточным буфером точно так же есть риск указать недостаточную длину:
string Text;
int Len = ::GetWindowTextLength(hEditControl);
if (Len != 0)
{
  vector<char> Buf(Len); // программист забыл '+ 1' для завершающего '\0'
  ::GetWindowText(hEditControl, &Buf[0], Len + 1);
  Text.assign(&Buf[0], Len);
}


A_A>Кроме того, как тогда быть с выделенным:

А>>Фактически это работает, но стандарт не гарантирует, что string хранит char-ы последовательно в одном массиве (кстати, для vector-а такая гарантия есть (не считая vector<bool>)). Теоретически, string может хранить char-ы задом наперёд или в нескольких массивах (как deque).
Такая возможность дана string-у для оптимизации (например, конкатенации). Но для оптимизации нужно знать особенности программы. string их не знает, это универсальный класс. Поэтому все известные мне реализации string-а не выпендриваются и хранят char-ы в одном монолитном буфере. Реализацию string-а в deque-стиле я видел только на картинке в книге Страуструпа "Язык программирования C++" (третье издание) в разделе 17.1.3 ("Представление" (STL-контейнеров)). Даже если доведётся использовать такую реализацию, то можно выбрать вариант GENERIC_STL_IMPL (с промежуточным буфером). Но скорее всего не доведётся, так как есть движение в сторону гарантии непрерывности char-ов в string-е.
Кстати, конкатенацию строк можно оптимизировать гораздо проще, заменив бинарный operator+ на "сколько-угодно-арную" функцию Concat. Об этом — пункт 10.4 (ниже).

А>>В будущем прямой доступ к буферу на запись может быть добавлен. Пока, проблему можно решить, введя дополнительный слой абстракции:

A_A><код поскипан>
А>>Настройками компиляции можно выбрать "законный" или быстрый вариант, не меняя код.
A_A>А если в одном месте кода нужно быстро, а в другом месте безопасно?
Вряд ли есть проект, в котором один класс использует MSVC6 STL, а другой — STLport. В большинстве проектов весь код использует одну и ту же реализацию STL. То, как реализован string, и определяет выбор между GENERIC_STL_IMPL и REALISTIC_STL_IMPL. Вариант REALISTIC_STL_IMPL (без промежуточного буфера) вполне безопасный и работает со всеми известными мне реализациями STL.

A_A>Лучше использовать отдельный класс для манипуляций со строками.

Да. Создатели крупных библиотек (MFC, Qt, wxWidgets, VCL) так и делают. Часто их "велосипедные" строковые классы эффективнее стандартного.

А>>10.4. Конкатенация строк

А>>Вместо бинарного operator+, сцепляющего строки по очереди, было бы оптимальнее (по скорости работы программы) использовать функцию Concat, сцепляющую несколько строк одним махом.
A_A>То же самое, для критичного по производительности кода лучше использовать специализированный класс для манипуляций со строками.
Создатель библиотеки не может точно узнать, где критичный по скорости код (а string предназначался в первую очередь для библиотек, как стандартная инфраструктура). Если пользователь редко вызывает функцию, то она не критична по скорости. Но другой пользователь может вызывать ту же функцию очень часто, тогда она станет критичной по скорости. В таких условиях лучше попусту не терять эффективность из-за неудачно спроектированного string-а. Поэтому создатели крупных библиотек делают собственные строковые классы. Создатели библиотек поменьше (например, WTL) перенимают удачные решения.

А>>В функции DoConcat можно использовать класс ostringstream в качестве string builder-а.

A_A>А вот это точно не стоит использовать в критичном по времени коде.
Допустим, я пишу переносимую библиотеку и мне нужно вернуть string из функции. Я знаю только два стандартных string builder-а:
Разве есть ещё?
По поводу медленности iostreams согласен полностью.

А>>11. У priority_queue нет метода increase_priority(element) (чтобы ускорить выталкивание элемента из очереди). Поэтому priority_queue нельзя использовать в алгоритме Дейкстры и A*. Я не знаю, как устроена структура данных 'пирамида' (heap, частично упорядоченное сортирующее дерево), так что не могу сказать, можно ли безболезненно добавить increase_priority.

A_A>Скорее всего, increase_priority придется сначала находить нужный элемент, удалять его и вставлять повторно с новым приоритетом.
Возможно, я хочу слишком много от priority_queue. Этот пункт можно вычеркнуть из "Проблем STL-контейнеров" (тем более что priority_queue — не контейнер, а всего лишь адаптер контейнера). В конце концов, есть Boost Graph Library, сделанная в STL-стиле.
Пётр Седов (ушёл с RSDN)
Re[4]: Проблемы STL-контейнеров
От: kan Великобритания  
Дата: 15.09.06 08:24
Оценка:
Пётр Седов wrote:

> А>>3. Нет понятия 'NULL-итератор'. В качестве итератора, "ссылающегося в

> никуда", обычно используют Container.end(). Но вместо кода:
А как имея интервал [begin, NULL), вместо [begin, end) обойти его в обратном направлении (итератор bidirectional)?
Или получить 3-ий с конца элемент (итератор random-access)? Или даже просто посчитать длину интервала (итератор
random-access)?
Posted via RSDN NNTP Server 2.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: минусы STL
От: strcpy Россия  
Дата: 15.09.06 15:52
Оценка:
Здравствуйте, gid_vvp, Вы писали:

_>Hi All


_>перечислите, на ваш взгляд, основные минусы STL

_>принимаются только ответы с аргументацией

Мало квалифицированнах программистов, которые владеют STL.
Поэтому код на STL рискует стать трудносопровождаемым.
Удвой число ошибок, если не получается добиться цели.
Re[2]: минусы STL
От: remark Россия http://www.1024cores.net/
Дата: 15.09.06 16:32
Оценка: -2
Здравствуйте, Шахтер, Вы писали:

Ш>У STL много минусов (про некоторые из них можно поискать на форуме), но главный минус (из которого вытекают и все остальные) один -- эта библиотека перестала развиваться. По политическим соображениям STL включили в стандарт. Это привело к замораживанию интерфейса библиотеки (который далек от идеального). Конкретные реализации тоже плохо эволюционируют и не в том направлении. Фактически мы имеем дело с мертвой вещью. Ближайший аналог -- MFC.

Ш>Использовать мертвую вещь для новых проектов не стоит.

Из твоих размышлений неявно вытекает, что ты предлагаешь вообще не пользоваться промышленными и стандартизированными решениями... мда... комментарии излишни...
На MFC, кстати, реализовано _очень_ много ПО, и ничего, работает.


Ш>Что это за алгоритмы? А никто не знает -- произвол реализации. Хотя в том же Кнуте вагон алгоритмов сортировки.

Ш>И библиотека, претендующая на стандартную должна не фиг знает что содержать, а чисто конкретно -- sort 2, sort 3, sort 4, ..., quick sort, merge sort,
Ш>intro sort, quick random pivot sort и.т.д.

Ш>3) Контейнеры. Все STL контейнеры расчитаны на копирабельные типы. Это огромный минус. Причем совершенно непонятно, зачем такое ограничение. Никаких технических причин для этого нет. Сами контейнеры. Практически единственный мало-мальски полезный класс -- vector. А где списки? Я знаю много разных типов списков. Где они все? А где деревья? RB-tree AVL-tree B-tree ? Похоже, авторам так и не удалось прочитать Кнута. IQ не хватило.


А никто и не ставил задачу реализовать набор примеров к книге Кнута.
Если очень надо, я думаю такие наборы примеров уже есть.
Если ты недавно прочитал Кнута и находишься под впечатлением от того, что там написано, то это не значит, что все должны бросится это реализовывать и использовать
Я думаю, я не сильно ошибусь, если скажу, что больше чем половине программистов в 90% случаев хватает возможностей, предоставляемых стандартной библиотекой С++. Если я гружу из БД пару десятков записей и помещаю в контейнер, то абсолютно всё равно в какой контейнер я их помещю и каким sort'ом я их буду сортировать, хоть sort 15, хоть sort 78. Производительности это не прибавит ни на йоту.
Ну в оставшихся случаях (когда не хватает возможностей библиотеки) программисту не поможешь, т.к. на все случаи жизни все равно контейнеры и алгоритмы не реализуешь.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[4]: минусы STL
От: Аноним  
Дата: 15.09.06 16:55
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>И аллокировать каждый объект в динамической памяти?

Ш>Нет, спасибо.

А Вы любите хранить все на стеке? Было бы интересно посмотреть на стабильность такой программы (при условии, что это не Hello world). А "заряжать" многомегабайтные стеки для многопоточного приложения — это чревато, знаете ли. Память велика, но не безгранична.
Re[5]: минусы STL
От: Cyberax Марс  
Дата: 15.09.06 17:10
Оценка: +1
Аноним wrote:
> Ш>И аллокировать каждый объект в динамической памяти?
> Ш>Нет, спасибо.
> А Вы любите хранить все на стеке?
Я очень люблю — так как это очень быстро.

> Было бы интересно посмотреть на

> стабильность такой программы (при условии, что это не Hello world). А
> "заряжать" многомегабайтные стеки для многопоточного приложения — это
> чревато, знаете ли. Память велика, но не безгранична.
Интересно посмотреть на программы с сотнями потоков.
Posted via RSDN NNTP Server 2.0
Sapienti sat!
Re[5]: минусы STL
От: remark Россия http://www.1024cores.net/
Дата: 15.09.06 17:13
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Шахтер, Вы писали:


Ш>>И аллокировать каждый объект в динамической памяти?

Ш>>Нет, спасибо.

А>А Вы любите хранить все на стеке? Было бы интересно посмотреть на стабильность такой программы (при условии, что это не Hello world). А "заряжать" многомегабайтные стеки для многопоточного приложения — это чревато, знаете ли. Память велика, но не безгранична.



Тут ты не прав. Если контейнер хранит свои элементы по значению, это ещё не значит, что всё это располагается на стеке.
Сравни:

shared_ptr<vector<some> >


и

vector<shared_ptr<some> >


В обоих случаях объекты some хранятся в "куче". При этом первый вариант имо даже предпочтительнее, т.к. на стеке занимается меньше памяти, да и вообще меньше памяти занимается, при этом контейнер хранит эл-ты по значению!



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: Два типа языков
От: remark Россия http://www.1024cores.net/
Дата: 15.09.06 17:20
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, Шебеко Евгений, Вы писали:


ШЕ>> Блин, а давайте ещё поспорим на тему "Какие недостатки у С++"


E>1) Очень сложный язык, очень гибкий, с нереально сложным синтаксисом. Это приводит к тому, что для коллективного программирования на C++ абсолютно необходима инструкция.

E>А для написания хорошего компилятора нужен довольно большой НИИ

[skip]

E>Хватит?


Как сказал Страуструп:

There are only two kinds of languages: the ones people complain about and the ones nobody uses


Существует два типа языков: языки, которые все ругают, и языки, которые никто не использует


Имхо для языка бОльшая честь относится к первому типу, чем ко второму


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: минусы STL
От: IID Россия  
Дата: 16.09.06 01:14
Оценка: +1
Здравствуйте, strcpy, Вы писали:

S>Мало квалифицированнах программистов, которые владеют STL.


Вы серъезно так думаете о С++ программистах ?
kalsarikännit
Re[3]: минусы STL
От: Erop Россия  
Дата: 16.09.06 05:39
Оценка: 1 (1)
Здравствуйте, IID, Вы писали:

S>>Мало квалифицированнах программистов, которые владеют STL.

IID>Вы серъезно так думаете о С++ программистах ?
мало того, и сам C++ досконально тоже мало кто знает
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: минусы STL
От: strcpy Россия  
Дата: 16.09.06 11:57
Оценка:
IID>Вы серъезно так думаете о С++ программистах ?

У нас в мухоср... Новосибирске ситуация именно такая.
Удвой число ошибок, если не получается добиться цели.
Re[4]: минусы STL
От: strcpy Россия  
Дата: 16.09.06 11:58
Оценка: +1
E>мало того, и сам C++ досконально тоже мало кто знает
А это не возможно.
Потому что знание С++ завязано на знании интерпретации стандарта конкретным компилятором. Я работал с 5 компиляторами С++ (gcc, msvc, xlc, bc3.1, watcom c). Все они понимают С++ по-своему. Сам по себе язык не существует пока, к сожалению.
Удвой число ошибок, если не получается добиться цели.
Re[4]: Два типа языков
От: strcpy Россия  
Дата: 16.09.06 12:00
Оценка: +1
R>Имхо для языка бОльшая чАсть относится к первому типу, чем ко второму
Это слишком грубая дихотомия.
На деле есть языки, которые используются до сих пор в своих областях, например FORTRAN. Он достаточно неплохо переносим (не 100%, конечно) и имеет неплохую защиту от дурака.
Удвой число ошибок, если не получается добиться цели.
Re[3]: минусы STL
От: remark Россия http://www.1024cores.net/
Дата: 17.09.06 08:38
Оценка: 9 (1) +2
Здравствуйте, IID, Вы писали:

IID>Здравствуйте, strcpy, Вы писали:


S>>Мало квалифицированнах программистов, которые владеют STL.


IID>Вы серъезно так думаете о С++ программистах ?



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

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

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



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: минусы STL
От: Sni4ok  
Дата: 17.09.06 09:47
Оценка:
довльно неудобно, что *std::map::iterator ссылается на std::pair<key, value>, а не на само value, в связи с этим очнь сложно использовать её со стандартными алгоритмами и биндом, благо в boost::ptr_map этого недостатка нет.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.