Re[6]: минусы STL
От: kan_izh Великобритания  
Дата: 30.08.06 08:26
Оценка:
Kluev wrote:

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

>> > Нет, спасибо.
> _>А как ты тогда себе представляешь динамический массив без возможности
> скопировать элемент?
> _>Вроде только если будут move constructor... это обещают в следующей
> версии языка... но...
> Некопируемые типы в векторе неуместны, так же как копируемые в
> node-based контейнерах (map,list)
> Более того node-based контейнеры по хорошему должны быть интрузивными, в
> противном случае они не юзабельные.
Ну это скоро в бусте появится, скорее всего: http://www.boost.org/more/report-jan-2006.html "Intrusive Containers". Но
скачать и использовать уже можно.
И в след. версии языка тоже рассматривают.
Posted via RSDN NNTP Server 2.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: минусы STL
От: Какая разница Украина  
Дата: 05.09.06 11:46
Оценка:
Здравствуйте, gid_vvp, Вы писали:

_>Hi All


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

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

мое имхо — зачем там нужен std::valarray ?
кто-то его реально юзает?
есть ли от него польза?
!0xDEAD
Re[4]: минусы STL
От: Nose Россия  
Дата: 05.09.06 13:41
Оценка: :)
Здравствуйте, Константин Л., Вы писали:

КЛ>Здравствуйте, kan_izh, Вы писали:


КЛ>тензоры


ну уж нет тензор это не просто упорядоченный набор чисел -- он еще должен подчиняться определенным правилам при смене системы координат

И количество имрений тут тоже ни причем, т.к. скаляр например -- частный случай тензора
Re[5]: минусы STL
От: Константин Л. Франция  
Дата: 05.09.06 14:00
Оценка: +1
Здравствуйте, Nose, Вы писали:

N>Здравствуйте, Константин Л., Вы писали:


КЛ>>Здравствуйте, kan_izh, Вы писали:


КЛ>>тензоры


N>ну уж нет тензор это не просто упорядоченный набор чисел -- он еще должен подчиняться определенным правилам при смене системы координат


N>И количество имрений тут тоже ни причем, т.к. скаляр например -- частный случай тензора


и что? что я неверно сказал?
Re[2]: минусы STL
От: LaptevVV Россия  
Дата: 05.09.06 16:46
Оценка: :)
Здравствуйте, Какая разница, Вы писали:

КР>Здравствуйте, gid_vvp, Вы писали:


_>>Hi All


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

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

КР>мое имхо — зачем там нужен std::valarray ?

КР>кто-то его реально юзает?
КР>есть ли от него польза?
Предполагалось, что это будет библиотека для работы с многомерными массивами, реализованная максимально эффективно... В частности, обработка матриц там есть... Однако, как заметил, кажется Джосаттис, люди, работавшие над этой библиотекой, давно ушли и занялись другими делами, поэтому библиотека брошена...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: минусы STL
От: Alex_Avr Россия  
Дата: 05.09.06 18:25
Оценка: :)
Здравствуйте, shank, Вы писали:

S>Не надо далеко ходить... Нет способа преобразовать число в std::string и обратно. Так, чтобы было одновременно быстро, удобно и безопасно.


Может boost::lexical_cast + performance upgrade
Автор: korzhik
Дата: 02.09.06
?
С уважением, Александр Авраменко.
Re: Проблемы STL-контейнеров
От: Аноним  
Дата: 05.09.06 20:09
Оценка: 117 (13) +1 -3
Здравствуйте, gid_vvp, Вы писали:

_>Hi All


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

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

1. Все STL-контейнеры требуют копируемость элементов. Но часто объекты в программе обладают идентичностью. Неявное копирование для них бессмысленно, а потому запрещено. Чтобы хранить такие объекты в STL-контейнере, их обычно создают динамически (new), а в контейнер помещают указатели (обычные или умные, как boost::shared_ptr). Но было бы лучше (по скорости работы программы и по расходу памяти) избежать такой косвенности. В будущем это вполне возможно, если node-based контейнеры (list, map) научатся инициализировать элементы значением произвольного типа (а не только 'const value_type&', как сейчас). Тогда можно будет писать:
class File
{
  // неявное копирование запрещено
  File(const File& s);
  File& operator=(const File& s);
public:
  struct Params
  {
    string Path;
    int ShareMode;
  };
  explicit File(const Params& p);
private:
  ...
};

class FileList
{
  // неявное копирование запрещено
  FileList(const FileList& s);
  FileList& operator=(const FileList& s);
public:
  void AddFile(const string& Path, int ShareMode);
private:
  list<File> m_Files;
};

void FileList::AddFile(const string& Path, int ShareMode)
{
  File::Params p;
  p.Path = Path;
  p.ShareMode = ShareMode;
  m_Files.push_back(p);
}

Когда-то давно обобщённый push_back планировался, но был отвергнут из-за слабой поддержки member templates тогдашними компиляторами. Сейчас с этим гораздо лучше.
Также был бы удобен push_back без параметров, инициализирующий новый элемент конструктором по умолчанию.
Проблему упомянули Шахтер здесь
Автор: Шахтер
Дата: 23.08.06
(пункт 3), Kluev здесь
Автор: Kluev
Дата: 30.08.06
.

2. Все STL-контейнеры неявно копируются. Опасно, если неявная операция дорога. Например, нужно вывести список строк на консоль:
void PrintItems(const list<string>& Items)
{
  for (list<string>::const_iterator it = Items.begin(); it != Items.end(); ++it)
  {
    cout << *it << '\n';
  }
}

Этот код работает нормально. Но если программист забудет '&' после 'const list<string>', то список будет передаваться уже не по ссылке, а по значению. При этом программа будет компилироваться без предупреждений и правильно работать. Лишнее копирование списка может остаться незамеченным.
За примером далеко ходить не надо. Два года назад некто gunf прислал в форум 'Мультимедиа, графика, звук' следующий код (корневое сообщение
Автор: gunf
Дата: 21.06.04
):

class POINT3D
{
private:
   double getxyz(int i);
   void   setxyz(int i,double v);
protected:

public:
    double x,y,z;
   POINT3D():x(0),y(0),z(0){};
   __property double xyz[int] = { read = getxyz , write = setxyz };
};
typedef std ::vector<POINT3D,std::allocator<POINT3D> >LINE;
typedef std ::vector<LINE,std::allocator<LINE> >SURF;
...
 void __fastcall TMyPanel::Mesh    (SURF matr)
{
     for (int i = 0; i<int(matr.size()); i++)
        {
         for (int j = 1; j<int(matr[i].size()); j++)
          {
         glBegin(GL_LINES);
         glVertex3d(matr[i][j-1].x,matr[i][j-1].y,matr[i][j-1].z);
         glVertex3d(matr[i][j].x,matr[i][j].y,matr[i][j].z);
         glEnd();
          }
        }
}

Никто из высказавшихся на форуме не заметил отсутствия '&'. gunf сказал, что у него "около 100 000 точек".
Было бы удобно, если бы компилятор выдавал предупреждение при передаче STL-контейнера по значению. Запретить это в будущих версиях STL нельзя, так как старый код может перестать компилироваться. Для string-а неявное копирование желательно, так как он часто бывает результатом функции (например, метод ostringstream::str).
MFC-контейнеры CArray, CList и CMap наследуют от CObject. Для CObject неявное копирование запрещено:

class CObject
{
  ...
  // Disable the copy constructor and assignment by default so you will get
  //   compiler errors instead of unexpected behaviour if you pass objects
  //   by value or assign objects.
protected:
  CObject();
private:
  CObject(const CObject& objectSrc);              // no implementation
  void operator=(const CObject& objectSrc);       // no implementation
  ...
};

Соответственно, оно запрещено и для всех наследников CObject, включая CArray, CList и CMap. CString неявно копируется (в MFC 4.2 дёшево).

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-итератором лучше по всем показателям:
(Естественно, при условии вменяемой реализации.)
Иногда бывает так, что контейнера ещё нет, а итератор уже есть. Например, объект, содержащий контейнер, ещё не создан. Тогда не получится инициализировать итератор end-ом. Придётся оставить итератор неинициализированным. Если программист случайно использует такой итератор, то есть риск тихой порчи памяти. Если бы была возможность инициализировать итератор NULL-ом, то смерть программы была бы быстрой и безболезненной (access violation).
Теоретически, можно завести липовый статический контейнер только ради того, чтобы иметь с него end-итератор. Но. Во-первых, это криво. Во-вторых, строго говоря, нельзя сравнивать итераторы от разных контейнеров (это логическая ошибка, хотя фактически работает).
Обычно итераторы — это обёртки над указателями:
template<typename TElem, ...>
class vector
{
  ...
  typedef TElem* iterator;
  ...
};

template<typename TElem, ...>
class list
{
  ...
  struct Node
  {
    Node* pPrev;
    Node* pNext;
    TElem Elem;
  };
  ...
  class iterator
  {
    Node* m_pNode;
    ...
  };
  ...
};

Поэтому понятие 'NULL-итератор' можно безболезненно ввести в STL. MSVC6 STL "поддерживает" NULL-итераторы.
В MFC-контейнерах CList и CMap для итерации используется тип POSITION. Это typedef указателя:

// abstract iteration position
struct __POSITION { };
typedef __POSITION* POSITION;

Поэтому POSITION вполне законно может быть NULL. Хотя такой вариант итераторов не type-safe, так как для всех конкретизаций CList и CMap используется один и тот же тип.

4. STL-контейнеры провоцируют использование size_t. Беззнаковость этого типа влечёт проблемы.
Два года назад Кирпа В.А. сообщил в этот форум о своей проблеме (корневое сообщение
Автор: Кирпа В.А.
Дата: 20.09.04
). У него был такой код:
pCmdUI->Enable(pFrame->m_UndoRedo.GetPos() < pFrame->mUndoRedo.buffer.GetSize() - 1);

GetPos возвращает int(-1)
buffer.GetSize возвращает int(1) (buffer — это CArray)
Выражение вычисляется так:
int(-1) < int(1) — 1 =>
int(-1) < int(0) =>
true
После замены CArray на vector:
GetPos возвращает int(-1)
buffer.size возвращает uint(1)
Тогда:
int(-1) < uint(1) — 1 =>
int(-1) < uint(0) =>
uint(4294967295) < uint(0) =>
false
Этот код разрешал/запрещал кнопку на toolbar-е, поэтому ошибка быстро обнаружилась. Приведение типа решило проблему:
pCmdUI->Enable(pFrame->m_UndoRedo.GetPos() < (int) pFrame->mUndoRedo.buffer.size() - 1);

Выражение стало вычисляться как раньше.
Другой пример. Страуструп в книге "Язык программирования C++" (третье издание) в разделе 17.6 разрабатывает hash_map в стиле STL-контейнера. В разделе 17.6.2.2 приведён следующий код:

template<...>
void hash_map::resize(size_type s)
{
  ...
  if (no_of_erased) {
    for (size_type i = v.size() - 1; 0 <= i; i--)
      if (v[i].erased) {
        v.erase(&v[i]);
        if (--no_of_erased == 0) break;
      }
  }
  ...
}

Если size_type = size_t (по умолчанию), то условие "0 <= i" бессмысленно, так как значение переменной беззнакового типа всегда >= 0. Как ни странно, код работает правильно, так как no_of_erased — это количество элементов vector-а v, у которых erased = true.
Здесь Страуструп стал жертвой "STL-стиля". Сам он всячески призывает программиста использовать int. Та же книга, раздел 4.4 ("Целые типы"):

Типы unsigned (без знака) идеально подходят для задач, в которых память интерпретируется как массив битов. Использование unsigned вместо int с целью заработать лишний бит для представления положительных целых почти всегда оказывается неудачным решением. Использование же объявления unsigned для гарантии того, что целое будет неотрицательным, почти никогда не сработает из-за правил неявного преобразования типов (§ В.6.1, § В.6.2.1).

Именно эти правила неявного преобразования типов "подменили" -1 на 4294967295 в программе Кирпы.
В разделе 4.10 ("Советы"):

[18] Избегайте беззнаковой арифметики; § 4.4.
[19] С подозрением относитесь к преобразованиям из signed в unsigned и из unsigned в signed; § В.6.2.6.

Какое отношение size_t имеет к array-based контейнерам (vector, string), ещё понятно: в языках C/C++ длина/индекс массива выражаются типом size_t. Какое отношение size_t имеет к node-based контейнерам (list, map), непонятно совсем. В 90-ые годы (время проектирования STL), когда ещё живы были 16-битные платформы, использование size_t было оправдано. Сейчас это источник тонких ошибок.
"Проблему size_t" можно решить несколькими способами:
В MFC-контейнерах количество элементов и индекс массива выражаются типом int:

template<...>
class CArray : public CObject
{
  ...
  int GetSize() const;
  ...
  // overloaded operator helpers
  TYPE operator[](int nIndex) const;
  TYPE& operator[](int nIndex);
  ...
};

template<...>
class CList : public CObject
{
  ...
  // count of elements
  int GetCount() const;
  ...
};

template<...>
class CMap : public CObject
{
  ...
  // number of elements
  int GetCount() const;
  ...
};

class CString
{
  ...
  // get data length
  int GetLength() const;
  ...
  // return single character at zero-based index
  TCHAR operator[](int nIndex) const;
  ...
};

Реализации operator[] проверяют (ASSERT-ами), что индекс >= 0. В преддверии Win64 int обобщили до INT_PTR.

5. Нельзя получить итератор по указателю на элемент контейнера за константное время. Например, есть такой код:
class Item
{
public:
  void AddRef();
  void Release();
  string Name() const;
private:
  friend class ItemManager;
  ItemManager* m_pManager;
  int m_Refs;
  string m_Name;
  Item(ItemManager* pManager, const string& Name);
};

class ItemManager
{
public:
  Item* AddItem(const string& Name);
private:
  friend class Item;
  list<Item> m_Items;
  void RemoveItem(Item* pItem);
};

Item* ItemManager::AddItem(const string& Name)
{
  m_Items.push_back(Item(this, Name));
  return &m_Items.back();
}

void ItemManager::RemoveItem(Item* pItem)
{
  // получить итератор по указателю на элемент списка
  list<Item>::iterator it = m_Items.begin();
  while (&*it != pItem)
  {
    ++it;
  }
  m_Items.erase(it);
}

Item::Item(ItemManager* pManager, const string& Name) :
  m_pManager(pManager),
  m_Refs(1),
  m_Name(Name)
{
}

void Item::AddRef()
{
  m_Refs++;
}

void Item::Release()
{
  m_Refs--;
  if (m_Refs == 0)
  {
    m_pManager->RemoveItem(this);
  }
}

Желательно, чтобы код, выделенный жирным, работал за константное время. То есть чтобы в STL было что-нибудь вроде:
template<typename TElem, ...>
class vector
{
  ...
  typedef TElem* iterator;
  ...
  static iterator iterator_from_pointer(TElem* pElem)
  {
    return pElem;
  }
  ...
};

template<typename TElem, ...>
class list
{
  ...
  struct Node
  {
    Node* pPrev;
    Node* pNext;
    TElem Elem;
  };
  ...
  class iterator
  {
    Node* m_pNode;
    ...
  };
  ...
  static iterator iterator_from_pointer(TElem* pElem)
  {
    iterator it;
    it.m_pNode = reinterpret_cast<Node*>(reinterpret_cast<Byte*>(pElem) - offsetof(Node, Elem));
    assert(&it.m_pNode->Elem == pElem);
    return it;
  }
  ...
};


6. Вместо метода empty удобнее был бы метод с позитивным смыслом, например has_elems (возвращает true <=> в контейнере есть хотя бы один элемент). Можно сделать обёртку над контейнером:
template<typename TElem>
class MyList : public list<TElem>
{
public:
  bool has_elems() const { return !empty(); }
};


7. Нельзя const_cast const_iterator в iterator. Я с этой проблемой на практике не сталкивался. Теоретически, может возникнуть при неудачном дизайне.

8. vector
8.1. Странное название у этого контейнера. В математике вектор содежит фиксированное количество элементов. Поэтому математический вектор похож на Pascal-евский массив, для которого "размер массива является частью его типа". Поначалу слово 'vector' может сбить с толку.
В MFC соответствующий контейнер называется 'CArray'.
Проблему упомянул LaptevVV здесь
Автор: LaptevVV
Дата: 25.08.06
.

8.2. Динамический "массив битов" лучше было бы оформить как отдельный класс (например, dyn_bit_array), а не как специализацию vector<bool>. Нынешнее решение нарушает обобщённость vector-а. В будущем, возможно, динамический "массив битов" выделят в отдельный класс и обобщённость vector-а будет восстановлена.
Проблему упомянули Mazay здесь
Автор: Mazay
Дата: 23.08.06
, Шебеко Евгений здесь
Автор: Шебеко Евгений
Дата: 23.08.06
.

Всё это странно, так как Страуструп в книге "Дизайн и эволюция языка C++" пишет (в самом конце 8-ой главы):

Одобрен также класс динамического массива dynarray [Stal, 1993], шаблон класса bits<N> для битовых множеств фиксированного размера, класс bitstring для битовых множеств с изменяемым размером. Кроме того, комитет принял классы комплексных чисел (предшественник — первоначальный класс complex, см. раздел 3.3), обсуждается вопрос, следует ли принять класс вектора для поддержки численных расчётов и научных приложений. О наборе стандартных классов, их спецификациях и даже названиях всё ещё ведутся оживлённые споры.

Там же есть сноска:

...
Разумеется, STL содержит классы для отображений и списков и включает в качестве частных случаев вышеупомянутые классы dynarray, bits и bitstring. Кроме того, комитет одобрил классы векторов для поддержки численных и научных расчётов, приняв за основу предложение Кена Баджа из Sandia Labs.

Видимо, в ходе стандартизации, кто-то, слабо знакомый с математикой, переименовал dynarray в vector и спрятал bitstring под маской vector<bool>. Может быть, повлияла традиция. Функцию main обычно пишут так:
int main(int argc, char* argv[])
{
  ...
}

argv означает 'argument vector'.

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
.

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

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

В MFC-шном CList-е методы AddHead и AddTail возвращают итератор:

  // add before head or after tail
  POSITION AddHead(ARG_TYPE newElement);
  POSITION AddTail(ARG_TYPE newElement);


10. string
10.1. Если программа много работает с текстом, то string (или его аналог) — ходовой тип. При этом желательно, чтобы неявное копирование string-а было дешёвым. Для этого реализация string-а может использовать разделяемый (между несколькими string-ами) буфер с подсчётом ссылок. Но string спроектирован в mutable стиле, поэтому у него есть не-const методы begin, end, rbegin, rend, operator[] и at. Если реализация string-а считает ссылки, то эти методы делают copy-on-write (даже если вы не собираетесь ничего менять) и запрещают разделяемость буфера. При этом неявное копирование string-а незаметно становится дорогой операцией (создание нового буфера и копирование туда char-ов). Чтобы случайно не запретить разделяемость буфера (если реализация string-а считает ссылки), можно сделать обёртку над string-ом в immutable стиле:
class ImmutString : public string
{
public:
  ImmutString(const char s[]) :
    string(s)
  {
  }

  ImmutString(const char s[], int L) :
    string(s, L)
  {
  }

  const_iterator begin() const
  {
    return string::begin();
  }

  const_iterator end() const
  {
    return string::end();
  }

  const_reverse_iterator rbegin() const
  {
    return string::rbegin();
  }

  const_reverse_iterator rend() const
  {
    return string::rend();
  }

  char operator[](int Index) const
  {
    return string::operator[](Index);
  }

  char at(int Index) const
  {
    return string::at(Index);
  }
};

Теперь не-const методы базового класса (string) скрыты одноимёнными методами производного класса (которые вызывают соответствующие const методы string-а).
У MFC-шного CString-а operator[] возвращает не ссылку на char (как у STL-ного string-а), а копию char-а:

class CString
{
  ...
  // return single character at zero-based index
  TCHAR operator[](int nIndex) const;
  // set a single character at zero-based index
  void SetAt(int nIndex, TCHAR ch);  
  ...
};

Если вы действительно хотите изменить содержимое CString-а, вы должны сказать это явно (метод SetAt). В MFC 4.2 CString считает ссылки, поэтому метод SetAt делает copy-on-write, но разделяемость буфера не запрещает.

10.2. Нет завершающего '\0'. Вот такой код:
string Text = "abc";
assert(Text[3] == '\0');

содержит логическую ошибку, так как '3' является ошибочным индексом для string-а с length = 3. Усердная реализация STL, проверяющая индекс, обнаружит это и сообщит об ошибке (с помощью assert-а или исключения). Если реализация STL беспечная (не проверяет индекс), то фактически код будет работать, так как '\0' в конец обычно ставят (чтобы иметь быструю реализацию метода c_str).
Иногда завершающий '\0' удобен при parsing-е. Например, нужно узнать, соответствует ли строка шаблону "Имя1::Имя2". Код в стиле языка C может быть такой:
bool IsValidMethodName_CStyle(const char pText[])
{
  int p = 0;
  // Имя1 не может начинаться с цифры
  if (IsLetterOrUnderscore(pText[p])) p++; else return false;
  while (IsLetterOrUnderscoreOrDigit(pText[p])) p++;
  if (pText[p] == ':') p++; else return false;
  if (pText[p] == ':') p++; else return false;
  // Имя2 не может начинаться с цифры
  if (IsLetterOrUnderscore(pText[p])) p++; else return false;
  while (IsLetterOrUnderscoreOrDigit(pText[p])) p++;
  return pText[p] == '\0';
}

Функции IsLetterOrUnderscore и IsLetterOrUnderscoreOrDigit возвращают false для '\0', поэтому завершающий '\0' служит барьером при parsing-е. При использовании string-а придётся добавить проверки индекса:
bool IsValidMethodName_STL(const string& Text)
{
  int p = 0;
  // Имя1 не может начинаться с цифры
  if ((p < Text.length()) && IsLetterOrUnderscore(Text[p])) p++; else return false;
  while ((p < Text.length()) && IsLetterOrUnderscoreOrDigit(Text[p])) p++;
  if ((p < Text.length()) && (Text[p] == ':')) p++; else return false;
  if ((p < Text.length()) && (Text[p] == ':')) p++; else return false;
  // Имя2 не может начинаться с цифры
  if ((p < Text.length()) && IsLetterOrUnderscore(Text[p])) p++; else return false;
  while ((p < Text.length()) && IsLetterOrUnderscoreOrDigit(Text[p])) p++;
  return p == Text.length();
}

"Лишние" проверки нудно писать, также они замедляют программу. Конечно, вместо 'IsValidMethodName_STL(Text)', можно писать 'IsValidMethodName_CStyle(Text.c_str())', но тогда усердная реализация STL не cможет проверять индекс. В будущем доступ к завершающему '\0' может быть разрешён.
К сожалению, MFC-шный CString запрещает доступ к завершающему '\0' (MFC 4.2):

_AFX_INLINE TCHAR CString::operator[](int nIndex) const
{
  // same as GetAt
  ASSERT(nIndex >= 0);
  ASSERT(nIndex < GetData()->nDataLength);
  return m_pchData[nIndex];
}


10.3. Нет прямого доступа к буферу на запись. Например, нужно получить введённый текст из edit control-а. Можно запросить текст "прямо в string":
string Text;
int Len = ::GetWindowTextLength(hEditControl);
if (Len != 0)
{
  Text.resize(Len);
  ::GetWindowText(hEditControl, &Text[0], Len + 1);
}

Фактически это работает, но стандарт не гарантирует, что string хранит char-ы последовательно в одном массиве (кстати, для vector-а такая гарантия есть (не считая vector<bool>)). Теоретически, string может хранить char-ы задом наперёд или в нескольких массивах (как deque). Даже если string хранит char-ы последовательно в одном массиве, теоретически место для завершающего '\0' может не резервироваться (а GetWindowText пишет его в буфер). У приведённого кода есть и практическая проблема: если реализация string-а считает ссылки, то не-const operator[] запрещает разделяемость буфера (между несколькими string-ами).
Можно воспользоваться методом c_str. Он предоставляет прямой доступ к буферу на чтение. Тут всё "честно": c_str возвращает указатель на массив char-ов, завершающийся '\0'. Код будет такой:
string Text;
int Len = ::GetWindowTextLength(hEditControl);
if (Len != 0)
{
  Text.resize(Len);
  ::GetWindowText(hEditControl, const_cast<char*>(Text.c_str()), Len + 1);
}

Фактически это тоже работает. Если реализация string-а считает ссылки, то, скорее всего, метод c_str не запрещает разделяемость буфера, доверчиво полагая, что вы не будете менять содержимое буфера. Но теоретически c_str-буфер может быть лишь копией последовательности char-ов, доступной через итераторы, operator[] и at. Если так, то изменение копии не затронет оригинал.
Единственный "законный" на данный момент способ — это использовать промежуточный буфер:
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);
}

Такое решение работает медленнее предыдущих вариантов и фрагментирует кучу (heap). Вместо универсальной кучи можно использовать специализированный менеджер памяти для временных выделений памяти. Иногда в таких случаях используют alloca, но это опасно, особенно в циклах (риск получить stack overflow).
Получается, что фактически быстрый код работает со всеми реализациями STL, но слишком обобщённый стандарт мешает жить совестливым программистам. А жить приходится бок о бок с C-style APIs (например, WinAPI). В будущем прямой доступ к буферу на запись может быть добавлен. Пока, проблему можно решить, введя дополнительный слой абстракции:
//#define GENERIC_STL_IMPL
//#define REALISTIC_STL_IMPL

class StringBuf
{
  // неявное копирование запрещено
  StringBuf(const StringBuf& s);
  StringBuf& operator=(const StringBuf& s);
public:
  StringBuf(string* pTarget, int Len);
  char* Chars();
  void Commit();
private:
#ifdef GENERIC_STL_IMPL
  string* m_pTarget;
  vector<char> m_Buf;
#endif
#ifdef REALISTIC_STL_IMPL
  string* m_pTarget;
#endif
};

#ifdef GENERIC_STL_IMPL

StringBuf::StringBuf(string* pTarget, int Len)
{
  assert(pTarget != NULL);
  assert(pTarget->empty()); // строим string с нуля
  assert(Len > 0);
  m_pTarget = pTarget;
  m_Buf.resize(Len + 1); // может понадобиться место для завершающего '\0'
}

char* StringBuf::Chars()
{
  assert(m_pTarget != NULL); // ещё не было Commit
  return &m_Buf[0];
}

void StringBuf::Commit()
{
  assert(m_pTarget != NULL); // ещё не было Commit
  m_pTarget->assign(&m_Buf[0], m_Buf.size() - 1); // без завершающего '\0'
  m_pTarget = NULL;
  m_Buf.clear(); // досрочно освободить память
}

#endif // GENERIC_STL_IMPL

#ifdef REALISTIC_STL_IMPL

StringBuf::StringBuf(string* pTarget, int Len)
{
  assert(pTarget != NULL);
  assert(pTarget->empty()); // строим string с нуля
  assert(Len > 0);
  m_pTarget = pTarget;
  m_pTarget->resize(Len);
}

char* StringBuf::Chars()
{
  assert(m_pTarget != NULL); // ещё не было Commit
  return const_cast<char*>(m_pTarget->c_str());
}

void StringBuf::Commit()
{
  assert(m_pTarget != NULL); // ещё не было Commit
  m_pTarget = NULL;
}

#endif // REALISTIC_STL_IMPL

Теперь код, достающий текст из edit control-а, будет такой:
string Text;
int Len = ::GetWindowTextLength(hEditControl);
if (Len != 0)
{
  StringBuf Buf(&Text, Len);
  ::GetWindowText(hEditControl, Buf.Chars(), Len + 1);
  Buf.Commit();
}

Настройками компиляции можно выбрать "законный" или быстрый вариант, не меняя код. Знание особенностей реализации string-а изолировано в классе StringBuf.
MFC-шный CString предоставляет прямой доступ к буферу на запись:
CString Text;
int Len = ::GetWindowTextLength(hEditControl);
if (Len != 0)
{
  char* pBuf = Text.GetBufferSetLength(Len);
  ::GetWindowText(hEditControl, pBuf, Len + 1);
  Text.ReleaseBuffer();
}

В MFC 4.2 CString считает ссылки. После прямого доступа к буферу на запись буфер может разделяться между несколькими CString-ами.

10.4. Конкатенация строк обозначается знаком '+'. Ещё одна программистская традиция, идущая вразрез с математикой. В MFC (и много где ещё) то же самое. В Visual Basic-е для этого используется '&', в script-овом языке Lua — '..' (две точки).
Вместо бинарного operator+, сцепляющего строки по очереди, было бы оптимальнее (по скорости работы программы) использовать функцию Concat, сцепляющую несколько строк одним махом. К счастью, такую функцию (точнее, семейство функций) можно сделать самому. Например:
struct ConcatString
{
  const char* pChars;
  int Len;

  ConcatString(const string& s)
  {
    pChars = s.c_str();
    Len = s.length();
  }

  // для строковых литералов
  ConcatString(const char s[])
  {
    pChars = s;
    Len = strlen(s);
  }
};

string DoConcat(const ConcatString pStrings[], int NumStrings)
{
  assert(NumStrings > 0);
  int TotalLen = 0;
  for (int i = 0; i < NumStrings; i++)
  {
    assert(pStrings[i].Len >= 0);
    TotalLen += pStrings[i].Len;
  }
  string Total;
  if (TotalLen != 0)
  {
    StringBuf Buf(&Total, TotalLen);
    char* pTotIter = Buf.Chars();
    for (int i = 0; i < NumStrings; i++)
    {
      ConcatString s = pStrings[i];
      if (s.Len != 0)
      {
        memcpy(pTotIter, s.pChars, s.Len * sizeof(char));
        pTotIter += s.Len;
      }
    }
    Buf.Commit();
  }
  return Total;
}

string Concat(ConcatString s1, ConcatString s2)
{
  const ConcatString Strings[] = { s1, s2 };
  return DoConcat(Strings, 2);
}

string Concat(ConcatString s1, ConcatString s2, ConcatString s3)
{
  const ConcatString Strings[] = { s1, s2, s3 };
  return DoConcat(Strings, 3);
}

// и так далее

Пример использования:
string DirPath = "C:\\Projects";
string FileName = "Notes.txt";
string FilePath = Concat(DirPath, "\\", FileName);

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

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

Пётр Седов
Re[3]: минусы STL
От: gid_vvp  
Дата: 06.09.06 07:25
Оценка: +1
A_A>Может boost::lexical_cast + performance upgrade
Автор: korzhik
Дата: 02.09.06
?


К сожалению это не STL...
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[6]: минусы STL
От: Nose Россия  
Дата: 06.09.06 08:01
Оценка: -1 :)
Здравствуйте, Константин Л., Вы писали:


КЛ>и что? что я неверно сказал?


_>Потому что одномерный array называется vector, двухмерный array называется matrix. Трёх и более специальных названий не

_>имеют (насколько я знаю).

КЛ>>>тензоры



Вот это утвеждение неверно, хотя я наверно занудствую
Re[2]: Проблемы STL-контейнеров
От: rusted Беларусь  
Дата: 06.09.06 09:02
Оценка: :)
Здравствуйте, Аноним, Вы писали:

А>4. STL-контейнеры провоцируют использование size_t. Беззнаковость этого типа влечёт проблемы.


проблемы влечет не сама беззнаковость size_t, а непоследовательность в использовании типов. и количество элементов в контейнере и индекс элемента не могут быть отрицательными — вполне логично делать их беззнаковыми. проблемы начинаются, когда в коде для них в одном месте используется знаковые типы, а в другом — беззнаковые.
зачем в вашем примере GetPos возвращает int? судя по всему только для возможности задать спец значение -1, на мой взгляд в таком случае более правильно использовать явно объявленое значение для беззнакового — как например std::string::npos.
Re[3]: Проблемы STL-контейнеров
От: Kluev  
Дата: 06.09.06 11:05
Оценка: +1 -1
Здравствуйте, rusted, Вы писали:

R>Здравствуйте, Аноним, Вы писали:


А>>4. STL-контейнеры провоцируют использование size_t. Беззнаковость этого типа влечёт проблемы.


R>проблемы влечет не сама беззнаковость size_t, а непоследовательность в использовании типов. и количество элементов в контейнере и индекс элемента не могут быть отрицательными — вполне логично делать их беззнаковыми. проблемы начинаются, когда в коде для них в одном месте используется знаковые типы, а в другом — беззнаковые.


Нет. Проблемма именно в беззнаковости stl.
пример:
for(i = 0, j = vec.size()-1; i < j; i++)
{
  // перебираем элементы парами, например для вычисления расстояния м-ду соседними
    vec[i]; vec[i+1];
}


Со знаковыми типами вот такой код будет нормально работать, а с бесзнаковыми i,j при vec.size()==0 будут грабли.

R>зачем в вашем примере GetPos возвращает int? судя по всему только для возможности задать спец значение -1, на мой взгляд в таком случае более правильно использовать явно объявленое значение для беззнакового — как например std::string::npos.


Для того чтобы можно было писать по человечески. Если например двигаемся в произвольном направлении и с произвольным шагом, то к индексу прибавляется приращение: i+=d, а приращение вполне может быть и отрицательным.
Re[3]: size_t
От: Пётр Седов Россия  
Дата: 06.09.06 11:08
Оценка: 1 (1) +2 -1
Здравствуйте, rusted, Вы писали:

R>Здравствуйте, Аноним, Вы писали:


А>>4. STL-контейнеры провоцируют использование size_t. Беззнаковость этого типа влечёт проблемы.


R>проблемы влечет не сама беззнаковость size_t, а непоследовательность в использовании типов. и количество элементов в контейнере и индекс элемента не могут быть отрицательными — вполне логично делать их беззнаковыми. проблемы начинаются, когда в коде для них в одном месте используется знаковые типы, а в другом — беззнаковые.

Логично использовать size_t до тех пор, пока не сядешь в лужу. С Вами, видимо, такого не случалось, а вот со мной случалось. Место жительства size_t — менеджеры памяти, лучше ему оттуда не расползаться по программе.
Создатели Java, наученные горьким опытом C++, вообще практически отказались от беззнаковых типов. uint32 в Java нет. Хотя это слишком радикальное решение: лечение головной боли путём отсечения головы. Но менеджеры памяти на Java не пишут, поэтому там это нормально.

R>зачем в вашем примере GetPos возвращает int? судя по всему только для возможности задать спец значение -1, на мой взгляд в таком случае более правильно использовать явно объявленое значение для беззнакового — как например std::string::npos.

Спросите у Кирпы. Иногда использование барьерных значений (как -1 здесь) позволяет не писать нудных граничных проверок.
Пётр Седов (ушёл с RSDN)
Re[4]: Проблемы STL-контейнеров
От: kan Великобритания  
Дата: 06.09.06 14:27
Оценка: 4 (1) +2
Kluev wrote:

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


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

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

> R>зачем в вашем примере GetPos возвращает int? судя по всему только для

> возможности задать спец значение -1, на мой взгляд в таком случае более
> правильно использовать явно объявленое значение для беззнакового — как
> например std::string::npos.
>
> Для того чтобы можно было писать по человечески. Если например двигаемся
> в произвольном направлении и с произвольным шагом, то к индексу
Для этого есть iterator.

> прибавляется приращение: i+=d, а приращение вполне может быть и

> отрицательным.
Для этого есть difference_type.
Posted via RSDN NNTP Server 2.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: Проблемы STL-контейнеров
От: Roman Odaisky Украина  
Дата: 06.09.06 16:26
Оценка: 7 (2) +1
Здравствуйте, Аноним, Вы писали:

А>1. Все STL-контейнеры требуют копируемость элементов. Но часто объекты в программе обладают идентичностью. Неявное копирование для них бессмысленно, а потому запрещено. Чтобы хранить такие объекты в STL-контейнере, их обычно создают динамически (new), а в контейнер помещают указатели (обычные или умные, как boost::shared_ptr). Но было бы лучше (по скорости работы программы и по расходу памяти) избежать такой косвенности. В будущем это вполне возможно, если node-based контейнеры (list, map) научатся инициализировать элементы значением произвольного типа (а не только 'const value_type&', как сейчас).


Наверное, было бы полезно, если сделать как следует (в частности, обеспечить type safety, отсутствие неявных преобразований). В C++09 проблема будет решена с помощью rvalue references.

А>2. Все STL-контейнеры неявно копируются. Опасно, если неявная операция дорога. Например, нужно вывести список строк на консоль:

А>Этот код работает нормально. Но если программист забудет '&' после 'const list<string>', то список будет передаваться уже не по ссылке, а по значению. При этом программа будет компилироваться без предупреждений и правильно работать. Лишнее копирование списка может остаться незамеченным.
Ориентируясь на забывчивых программеров, каши не сваришь. Хотя что-то в идее есть... Как только быть с функциями, возвращающими контейнер по значению?

А>3. Нет понятия 'NULL-итератор'.

Здесь не согласен.
typedef std::vector<X> Cont;
Cont cont;
Cont::const_iterator i = cont.end(); // decltype(i) = X*? Вполне возможно... или уже нет?
assert(i == std::null_iter);
--i; // и как?
++i; // и как?
assert(i == std::null_iter);

Другое дело, что многие итераторы имеют универсальным конечным итератором значение, создаваемое дефолтным конструктором (std::istream_iterator, наример). Лечится доработкой std::tr2::iterator_range.

А>4. STL-контейнеры провоцируют использование size_t. Беззнаковость этого типа влечёт проблемы.

Кривое использование вроде cont.size() — 1 всегда влечет проблемы. А если бы это были итераторы? boost::prev(cont.end())? Хотя здесь тоже сложно сказать однозначно. Если вставить cont.size() в какую-нибудь математическую формулу, можно чудеса получить... и я получал, когда делал что-то вроде кольцевого буфера:
int index = getIndex();
X& ref = vec[index % vec.size()]; // oops!


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

Я бы посмотрел на реализацию сего для unordered_map Без грязных хаков не обойтись, ты и сам привел примеры. И для чего это? Кому надо, выдаст наружу итераторы (typedef Cont::iterator ObjId; ObjId getSomeObject() {})

А>6. Вместо метода empty удобнее был бы метод с позитивным смыслом, например has_elems (возвращает true <=> в контейнере есть хотя бы один элемент).

ага

А>7. Нельзя const_cast const_iterator в iterator. Я с этой проблемой на практике не сталкивался. Теоретически, может возникнуть при неудачном дизайне.

Ну, это, если и проблема, то не STL, а языка.

А>8. vector

А>8.1. Странное название у этого контейнера. В математике вектор содежит фиксированное количество элементов. Поэтому математический вектор похож на Pascal-евский массив, для которого "размер массива является частью его типа". Поначалу слово 'vector' может сбить с толку.
если еще учесть std::tr1::array = boost::array, то вообще весело станет
Это не так важно, все привыкли уже.

А>8.2. Динамический "массив битов" лучше было бы оформить как отдельный класс (например, dyn_bit_array), а не как специализацию vector<bool>. Нынешнее решение нарушает обобщённость vector-а. В будущем, возможно, динамический "массив битов" выделят в отдельный класс и обобщённость vector-а будет восстановлена.

А>Проблему упомянули Mazay здесь
Автор: Mazay
Дата: 23.08.06
, Шебеко Евгений здесь
Автор: Шебеко Евгений
Дата: 23.08.06
.

...и Саттер, и Майерс, и кто только не ругал бедный std::vector<bool>...

А>Может быть, повлияла традиция. Функцию main обычно пишут так:

А>argv означает 'argument vector'.
я всегда думал, что 'argument values'

А>9. list

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

в чем проблема-то? Ты б хоть сформулировал ее. В (чрезмерной) мягкости стандарта?

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

ну может быть... сделать вроде легко.

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

можно...

А>10. string

Эх, не ругал только ленивый... Хуже всего, что есть за что

А>10.1. можно сделать обёртку над string-ом в immutable стиле:

можно

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

'\0' нужен только для legacy кода. Для нового есть end().

А>Иногда завершающий '\0' удобен при parsing-е. Например, нужно узнать, соответствует ли строка шаблону "Имя1::Имя2". Код в стиле языка C может быть такой:

А>Функции IsLetterOrUnderscore и IsLetterOrUnderscoreOrDigit возвращают false для '\0', поэтому завершающий '\0' служит барьером при parsing-е. При использовании string-а придётся добавить проверки индекса:
А>
bool IsValidMethodName_STL(const string& Text)
{
  int p = 0;
  ... ++p ... p++ ... if(2 * 2 == 5) p += 14; ...
  return p == Text.length();
}

Разве это STL style?
template <class II>
bool IsValidMethodName_RealStlStyle(II first, II last)
{
  II p = first;
  ... ++p ... p++ ... if(2 * 2 == 5) p += 14; ...
  return p == last;
}


А>10.3. Нет прямого доступа к буферу на запись. Например, нужно получить введённый текст из edit control-а. Можно запросить текст "прямо в string":

Опять же, для legacy кода?

А>10.4. Конкатенация строк обозначается знаком '+'. Ещё одна программистская традиция, идущая вразрез с математикой. В MFC (и много где ещё) то же самое. В Visual Basic-е для этого используется '&', в script-овом языке Lua — '..' (две точки).

А>Вместо бинарного operator+, сцепляющего строки по очереди, было бы оптимальнее (по скорости работы программы) использовать функцию Concat, сцепляющую несколько строк одним махом. К счастью, такую функцию (точнее, семейство функций) можно сделать самому. Например:

Здесь более в духе STL было бы что-то вроде join_iterator. Тоже можно самому написать.
До последнего не верил в пирамиду Лебедева.
Re[3]: Проблемы STL-контейнеров
От: kan Великобритания  
Дата: 06.09.06 16:57
Оценка:
Roman Odaisky wrote:

> А>2. Все STL-контейнеры неявно копируются. Опасно, если неявная операция

> дорога. Например, нужно вывести список строк на консоль:
> А>Этот код работает нормально. Но если программист забудет '&' после
> 'const list<string>', то список будет передаваться уже не по ссылке, а
> по значению. При этом программа будет компилироваться без предупреждений
> и правильно работать. Лишнее копирование списка может остаться незамеченным.
> Ориентируясь на забывчивых программеров, каши не сваришь. Хотя что-то в
> идее есть... Как только быть с функциями, возвращающими контейнер по
> значению?
Да нет, здесь всё ок. Тем более если осталось незамеченным — ради бога, пусть копируется жалко что-ли? Вдруг там список
обычно из 3 элементов и вызывается это раз в пятилетку? А если это источник тормозов — профайлер такие вещи показывает
на раз.

> А>6. Вместо метода empty удобнее был бы метод с позитивным смыслом,

> например has_elems (возвращает true <=> в контейнере есть хотя бы один
> элемент).
> ага
Фиолетово. Да и к тому же вариант с isEmpty или подобным — наиболее распространён.
"if(cont.size())", "если обладает размером"

> А>7. Нельзя const_cast const_iterator в iterator. Я с этой проблемой на

> практике не сталкивался. Теоретически, может возникнуть при неудачном
> дизайне.
> Ну, это, если и проблема, то не STL, а языка.
Я бы тоже не отказался, но не в таком виде, а в виде:
Cont cont = makeContainer();
Cont::const_iterator iter = chooseSomeElement(cont); // chooseSomeElement берёт на вход const Cont&, что логично, и 
может вернуть только const_iterator
Cont::iterator nonconst_iter = cont.make_nonconst(iter);// Вот такое хочу!
cont.erase(nonconst_iter);// иначе, например, такое не сделать. :(
Posted via RSDN NNTP Server 2.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: Проблемы STL-контейнеров
От: Alex_Avr Россия  
Дата: 07.09.06 05:41
Оценка: 2 (1)
Здравствуйте, Аноним, Вы писали:

А>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-итератором лучше по всем показателям:
Как-то не было проблем с чтением.
В современных IDE разницы практически нет.
Не факт, компилятор может встроить тело end () в место вызова.

А>Иногда бывает так, что контейнера ещё нет, а итератор уже есть. Например, объект, содержащий контейнер, ещё не создан. Тогда не получится инициализировать итератор end-ом. Придётся оставить итератор неинициализированным. Если программист случайно использует такой итератор, то есть риск тихой порчи памяти. Если бы была возможность инициализировать итератор NULL-ом, то смерть программы была бы быстрой и безболезненной (access violation).

А>Теоретически, можно завести липовый статический контейнер только ради того, чтобы иметь с него end-итератор. Но. Во-первых, это криво. Во-вторых, строго говоря, нельзя сравнивать итераторы от разных контейнеров (это логическая ошибка, хотя фактически работает).
А>Обычно итераторы — это обёртки над указателями:
А>
А>template<typename TElem, ...>
А>class vector
А>{
А>  ...
А>  typedef TElem* iterator;
А>  ...
А>};

А>template<typename TElem, ...>
А>class list
А>{
А>  ...
А>  struct Node
А>  {
А>    Node* pPrev;
А>    Node* pNext;
А>    TElem Elem;
А>  };
А>  ...
А>  class iterator
А>  {
А>    Node* m_pNode;
А>    ...
А>  };
А>  ...
А>};
А>

А>Поэтому понятие 'NULL-итератор' можно безболезненно ввести в STL. MSVC6 STL "поддерживает" NULL-итераторы.
Итераторы могут быть классами, а не простыми указателями (как в STLPort в отладочном билде),
в этом случае такой поход работать не будет.

А>В MFC-контейнерах CList и CMap для итерации используется тип POSITION. Это typedef указателя:

MFC — это совсем другая история, там нет обобщенных алгоритмов.

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

<код поскипан>
А>Желательно, чтобы код, выделенный жирным, работал за константное время.
А кто сказал, что это возможно для любых итераторов и любых контейнеров?

А>7. Нельзя const_cast const_iterator в iterator. Я с этой проблемой на практике не сталкивался. Теоретически, может возникнуть при неудачном дизайне.

Я тоже с такой проблемой не сталкивался.

А>8. vector

А>8.1. Странное название у этого контейнера.
Теперь его уже поздно менять .

А>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
.

То, что в одной из реализаций STL list::size () имеет линейную сложность еще ни очем не говорит.
Да и есть ли в CList аналог list::splice ()?

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

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

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

Гм, а почему нельзя просто использовать list::front () и list::back ()?
Мне кажется, что операции получения итераторов на элементы и вставка разделены не
просто так, а для того, чтобы можно было гарантировать предсказуемое состояние
программы в случае, если итератор является классом и при его копировании
может вылететь исключение:


class A { int n; };

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


А>В MFC-шном CList-е методы AddHead и AddTail возвращают итератор:

В MFC железно зашито, что POSITION является указателем.

А>10. string

А>10.1. Если программа много работает с текстом, то string (или его аналог) — ходовой тип. При этом желательно, чтобы неявное копирование string-а было дешёвым. Для этого реализация string-а может использовать разделяемый (между несколькими string-ами) буфер с подсчётом ссылок.
Сейчас , на сколько я знаю, отходят от использования COW при реализации std::string из-за проблем с многопоточностью,
к тому же это только одна из возможных реализаций. Все-таки как-то странно подгонять интерфейс под реализацию.
С другой стороны, может было бы лучше как в Java реализовать отдельно классы для неизменяемых строк и строк с возможностью изменения.

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

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

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

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

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

А>"Лишние" проверки нудно писать, также они замедляют программу. Конечно, вместо 'IsValidMethodName_STL(Text)', можно писать 'IsValidMethodName_CStyle(Text.c_str())', но тогда усердная реализация STL не cможет проверять индекс. В будущем доступ к завершающему '\0' может быть разрешён.

Ага, что бы пользователь класса сам мог этот нуль затереть и получить AV или SEGFAULT при вызове std::string::find например


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

Это позволяет string-у самому управляться со своими данными, что предотвращает проблемы некорректного с ними отношения со стороны
пользователя.
Например, нужно получить введённый текст из edit control-а. Можно запросить текст "прямо в string":
А>
А>string Text;
А>int Len = ::GetWindowTextLength(hEditControl);
А>if (Len != 0)
А>{
А>  Text.resize(Len);
А>  ::GetWindowText(hEditControl, &Text[0], Len + 1);
А>}
А>

Ну да, а если пользователь забыл вызвать resize () или указал недостаточный размер? Кроме того, как тогда быть
с выделенным:

А>Фактически это работает, но стандарт не гарантирует, что string хранит char-ы последовательно в одном массиве (кстати, для vector-а такая гарантия есть (не считая vector<bool>)). Теоретически, string может хранить char-ы задом наперёд или в нескольких массивах (как deque).


А>Получается, что фактически быстрый код работает со всеми реализациями STL, но слишком обобщённый стандарт мешает жить совестливым программистам. А жить приходится бок о бок с C-style APIs (например, WinAPI). В будущем прямой доступ к буферу на запись может быть добавлен. Пока, проблему можно решить, введя дополнительный слой абстракции:

<код поскипан>
А>Настройками компиляции можно выбрать "законный" или быстрый вариант, не меняя код.
А если в одном месте кода нужно быстро, а в другом месте безопасно?
Лучше использовать отдельный класс для манипуляций со строками.

А>10.4. Конкатенация строк обозначается знаком '+'. Ещё одна программистская традиция, идущая вразрез с математикой. В MFC (и много где ещё) то же самое. В Visual Basic-е для этого используется '&', в script-овом языке Lua — '..' (две точки).

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

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

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

А>Пётр Седов
С уважением, Александр Авраменко.
Re[3]: Проблемы STL-контейнеров
От: Пётр Седов Россия  
Дата: 07.09.06 06:22
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

А>>2. Все STL-контейнеры неявно копируются.

RO>Ориентируясь на забывчивых программеров, каши не сваришь. Хотя что-то в идее есть... Как только быть с функциями, возвращающими контейнер по значению?
Я иногда бываю "забывчивый". Так что для меня это проблема.
Если результат функции — контейнер, не string, то лучше его создать динамически (new), а вызывающему коду вернуть указатель (обычный или умный) на контейнер.
Я за то, чтобы неявные операции были дёшевы, а потенциально дорогие операции (например, копирование контейнера, не string) были явными. Так как string часто бывает результатом функции, то для него желательно дешёвое копирование.

А>>3. Нет понятия 'NULL-итератор'.

RO>Здесь не согласен.
RO>
RO>typedef std::vector<X> Cont;
RO>Cont cont;
RO>Cont::const_iterator i = cont.end(); // decltype(i) = X*? Вполне возможно... или уже нет?
RO>assert(i == std::null_iter);
RO>--i; // и как?
RO>++i; // и как?
RO>assert(i == std::null_iter);
RO>

Я имел в виду, чтобы NULL-итератор был не вместо end-итератора, а в дополнение к нему:
То, что для пустого vector-а эти два итератора равны, проблем создавать не должно.
Можно использовать boost::optional<iterator>, но лучше не усложнять.

RO>Другое дело, что многие итераторы имеют универсальным конечным итератором значение, создаваемое дефолтным конструктором (std::istream_iterator, наример). Лечится доработкой std::tr2::iterator_range.

Я имел в виду именно итераторы STL-контейнеров.

А>>4. STL-контейнеры провоцируют использование size_t. Беззнаковость этого типа влечёт проблемы.

RO>Кривое использование вроде cont.size() — 1 всегда влечет проблемы. А если бы это были итераторы? boost::prev(cont.end())? Хотя здесь тоже сложно сказать однозначно. Если вставить cont.size() в какую-нибудь математическую формулу, можно чудеса получить... и я получал, когда делал что-то вроде кольцевого буфера:
RO>
RO>int index = getIndex();
RO>X& ref = vec[index % vec.size()]; // oops!
RO>

Я предупредил начинающих, что под size_t грабли лежат. А уж использовать его или нет — пускай сами решают.

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

RO>Я бы посмотрел на реализацию сего для unordered_map Без грязных хаков не обойтись, ты и сам привел примеры. И для чего это? Кому надо, выдаст наружу итераторы (typedef Cont::iterator ObjId; ObjId getSomeObject() {})
Если unordered_map — это node-based контейнер, то его iterator_from_pointer будет такой же, как у list-а.
Можно сделать классы ItemManager и Item так, как Вы сказали:
class Item
{
public:
  string Name() const;
private:
  friend class ItemManager;
  int m_Refs;
  string m_Name;
  explicit Item(const string& Name);
};

class ItemManager
{
public:
  typedef list<Item>::iterator ItemHandle; // Вы назвали 'ObjId'
  ItemHandle AddItem(const string& Name); // Вы назвали 'getSomeObject'
  void AddRefItem(ItemHandle hItem);
  void ReleaseItem(ItemHandle hItem);
private:
  list<Item> m_Items;
};

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

void ItemManager::AddRefItem(ItemHandle hItem)
{
  hItem->m_Refs++;
}

void ItemManager::ReleaseItem(ItemHandle hItem)
{
  hItem->m_Refs--;
  if (hItem->m_Refs == 0)
  {
    m_Items.erase(hItem);
  }
}

Item::Item(const string& Name) :
  m_Refs(1),
  m_Name(Name)
{
}

Но. Во-первых, неудобно всюду таскать два значения (ItemManager* и ItemManager::ItemHandle) вместо одного (Item*). Во-вторых, именами 'AddRef' и 'Release' я намекал на технологию COM. Там не получится "выдать наружу итератор". Выдавать наружу придётся указатель на интерфейс (Item*). В этом случае STL-ный list без iterator_from_pointer проигрывает ручному списку в стиле языка C:
class Item
{
  // неявное копирование запрещено
  Item(const Item& s);
  Item& operator=(const Item& s);
public:
  void AddRef();
  void Release();
  string Name() const;
private:
  friend class ItemManager;
  ItemManager* m_pManager;
  Item* m_pPrev;
  Item* m_pNext;
  int m_Refs;
  string m_Name;
  Item(ItemManager* pManager, const string& Name);
};

class ItemManager
{
  // неявное копирование запрещено
  ItemManager(const ItemManager& s);
  ItemManager& operator=(const ItemManager& s);
public:
  ItemManager();
  ~ItemManager();
  Item* AddItem(const string& Name);
private:
  friend class Item;
  Item* m_pItemsHead;
  Item* m_pItemsTail;
  void RemoveItem(Item* pItem);
};

ItemManager::ItemManager() :
  m_pItemsHead(NULL),
  m_pItemsTail(NULL)
{
}

ItemManager::~ItemManager()
{
  Item* pPrev;
  for (Item* i = m_pItemsTail; i != NULL; i = pPrev)
  {
    pPrev = i->m_pPrev;
    delete i;
  }
}

Item* ItemManager::AddItem(const string& Name)
{
  Item* pItem = new Item(this, Name);
  // добавить *pItem в конец списка
  pItem->m_pPrev = m_pItemsTail;
  pItem->m_pNext = NULL;
  if (m_pItemsTail != NULL)
  {
    assert(m_pItemsTail->m_pNext == NULL);
    m_pItemsTail->m_pNext = pItem;
  }
  else
  {
    assert(m_pItemsHead == NULL);
    m_pItemsHead = pItem;
  }
  m_pItemsTail = pItem;
  return pItem;
}

// работает за константное время
void ItemManager::RemoveItem(Item* pItem)
{
  Item* pPrev = pItem->m_pPrev;
  Item* pNext = pItem->m_pNext;
  if (pPrev != NULL)
  {
    assert(pPrev->m_pNext == pItem);
    pPrev->m_pNext = pNext;
  }
  else
  {
    assert(m_pItemsHead == pItem);
    m_pItemsHead = pNext;
  }
  if (pNext != NULL)
  {
    assert(pNext->m_pPrev == pItem);
    pNext->m_pPrev = pPrev;
  }
  else
  {
    assert(m_pItemsTail == pItem);
    m_pItemsTail = pPrev;
  }
  delete pItem;
}

Item::Item(ItemManager* pManager, const string& Name) :
  m_pManager(pManager),
  m_Refs(1),
  m_Name(Name)
{
}

void Item::AddRef()
{
  m_Refs++;
}

void Item::Release()
{
  m_Refs--;
  if (m_Refs == 0)
  {
    m_pManager->RemoveItem(this);
  }
}


А>>7. Нельзя const_cast const_iterator в iterator. Я с этой проблемой на практике не сталкивался. Теоретически, может возникнуть при неудачном дизайне.

RO>Ну, это, если и проблема, то не STL, а языка.
Я неточно выразился. Я не про "перегрузку const_cast". Программист может явно убрать константность с указателя (с помощью const_cast). Итератор — это обобщение указателя, поэтому также может понадобиться снять константность с итератора. Не обязательно const_cast-ом, можно методом:
template<typename TElem, ...>
class vector
{
  ...
  typedef TElem* iterator;
  typedef const TElem* const_iterator;
  ...
  static iterator remove_const(const_iterator cit)
  {
    return const_cast<TElem*>(cit);
  }
  ...
};

template<typename TElem, ...>
class list
{
  ...
  struct Node
  {
    Node* pPrev;
    Node* pNext;
    TElem Elem;
  };
  ...
  class const_iterator
  {
    const Node* m_pNode;
    ...
  };
  ...
  class iterator
  {
    Node* m_pNode;
    ...
  };
  ...
  static iterator remove_const(const_iterator cit)
  {
    iterator it;
    it.m_pNode = const_cast<Node*>(cit.m_pNode);
    return it;
  }
  ...
};

Существующий вариант снятия константности:
const_iterator сit
distance(const begin, сit) => индекс ix
advance(begin, ix) => iterator it
смотрится криво.

А>>8. vector

А>>8.1. Странное название у этого контейнера.
RO>если еще учесть std::tr1::array = boost::array, то вообще весело станет
RO>Это не так важно, все привыкли уже.
+ valarray
Да, все привыкли к ещё одной программистской традиции, идущей вразрез с математикой. Одно из самых сложных действий в программировании — придумать хорошее имя для сущности. 'vector' — неудачное имя для динамического массива. Но это разговор для другого форума.

А>>8.2. vector<bool>

А>>Проблему упомянули Mazay здесь
Автор: Mazay
Дата: 23.08.06
, Шебеко Евгений здесь
Автор: Шебеко Евгений
Дата: 23.08.06
.

RO>...и Саттер, и Майерс, и кто только не ругал бедный std::vector<bool>...
vector<bool> — это странно. Авторы STL провозглашают обобщённость, но сами же нарушают обобщённость vector-а.

А>>Может быть, повлияла традиция. Функцию main обычно пишут так:

А>>argv означает 'argument vector'.
RO>я всегда думал, что 'argument values'
В Google я запросил поиск по слову 'argv'. В первой же строчке увидел:

The name of the variable argv stands for "argument vector".


А>>9. list

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

RO>в чем проблема-то? Ты б хоть сформулировал ее. В (чрезмерной) мягкости стандарта?
Во многих реализациях STL list::size очень дёшев, но в некоторых ("странных") реализациях STL list::size дорог. Если беспечно использовать дешёвый list::size, то при переходе на "странную" реализацию STL программа замедлится.

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

RO>ну может быть... сделать вроде легко.
Сделать что?

А>>10.1. можно сделать обёртку над string-ом в immutable стиле:

RO>можно
Этот пункт скомканный получился. Писал-то я не про обёртку, а про проблему. На примере будет яснее. Есть код, использующий MSVC6 STL (там string считает ссылки):
string CommandLineParam = "-help";
bool IsSwitch = false;
if (CommandLineParam[0] == '-')
{
  IsSwitch = true;
}
string Copy = CommandLineParam; // дорогое копирование (deep)
assert(Copy.c_str() != CommandLineParam.c_str()); // разные буферы

Невинно выглядящее условие в if-е делает две "вредные" вещи:
1. copy-on-write, хотя я не меняю содержимое CommandLineParam-а. В данном случае CommandLineParam — единственный владелец буфера, поэтому ничего не происходит.
2. Запрещает разделяемость буфера между несколькими string-ами. Это плохо, так как копирование CommandLineParam-а становится дорогим.
В MFC 4.2 CString тоже считает ссылки, но такой проблемы нет:
CString CommandLineParam = "-help";
bool IsSwitch = false;
if (CommandLineParam[0] == '-')
{
  IsSwitch = true;
}
CString Copy = CommandLineParam; // дешёвое копирование (shallow)
assert(static_cast<const char*>(Copy) == static_cast<const char*>(CommandLineParam)); // общий буфер

потому что CString::operator[] возвращает копию char-а (а не ссылку на char, как string::operator[]) и не запрещает разделяемость буфера.
Другой пример. Теперь есть код, использующий string::operator[] для изменения текста:
string FilePath = "C:/Projects/Notes.txt";
string::size_type SearchBegin = 0; // да, size_t
string::size_type SlashPos; // опять size_t
while ((SlashPos = FilePath.find('/', SearchBegin)) != string::npos)
{
  FilePath[SlashPos] = '\\';
  SearchBegin = SlashPos + 1;
}
assert(FilePath == "C:\\Projects\\Notes.txt");
string Copy = FilePath; // дорогое копирование (deep)
assert(Copy.c_str() != FilePath.c_str()); // разные буферы

В данном случае copy-on-write уместен (потому что я действительно меняю содержимое FilePath), но string::operator[] по-прежнему запрещает разделяемость буфера. CString::SetAt тоже делает copy-on-write, но не запрещает разделяемость буфера:
CString FilePath = "C:/Projects/Notes.txt";
int SearchBegin = 0;
int SlashPos;
while ((SlashPos = FilePath.Find('/', SearchBegin)) != -1)
{
  FilePath.SetAt(SlashPos, '\\');
  SearchBegin = SlashPos + 1;
}
assert(FilePath == "C:\\Projects\\Notes.txt");
CString Copy = FilePath; // дешёвое копирование (shallow)
assert(static_cast<const char*>(Copy) == static_cast<const char*>(FilePath)); // общий буфер


А>>10.2. Нет завершающего '\0'.

RO>'\0' нужен только для legacy кода. Для нового есть end().
Я имел в виду, что иногда барьер (завершающий '\0') позволяет не писать нудные граничные проверки ('p < Text.length()'). Раз уж все реализации string-а ставят '\0' в конец (для быстрого c_str), то, вероятно, стоит разрешить доступ к завершающему '\0' официально. Это может быть удобно и для нового кода.

А>>Иногда завершающий '\0' удобен при parsing-е. Например, нужно узнать, соответствует ли строка шаблону "Имя1::Имя2". Код в стиле языка C может быть такой:

А>>Функции IsLetterOrUnderscore и IsLetterOrUnderscoreOrDigit возвращают false для '\0', поэтому завершающий '\0' служит барьером при parsing-е. При использовании string-а придётся добавить проверки индекса:
А>>
RO>bool IsValidMethodName_STL(const string& Text)
RO>{
RO>  int p = 0;
RO>  ... ++p ... p++ ... if(2 * 2 == 5) p += 14; ...
RO>  return p == Text.length();
RO>}
RO>

RO>Разве это STL style?
RO>
RO>template <class II>
RO>bool IsValidMethodName_RealStlStyle(II first, II last)
RO>{
RO>  II p = first;
RO>  ... ++p ... p++ ... if(2 * 2 == 5) p += 14; ...
RO>  return p == last;
RO>}
RO>

Я не говорил, что это STL-style. Суффикс '_STL' (не '_STLStyle') означает, что функция работает с STL-ным string-ом.

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

RO>Опять же, для legacy кода?
Например, для получения текста от WinAPI, минуя промежуточный буфер. Но в основном текст идёт в обратном направлении: из программы в WinAPI. Тут c_str справляется хорошо.

А>>10.4. Конкатенация строк обозначается знаком '+'. Ещё одна программистская традиция, идущая вразрез с математикой. В MFC (и много где ещё) то же самое. В Visual Basic-е для этого используется '&', в script-овом языке Lua — '..' (две точки).

А>>Вместо бинарного operator+, сцепляющего строки по очереди, было бы оптимальнее (по скорости работы программы) использовать функцию Concat, сцепляющую несколько строк одним махом. К счастью, такую функцию (точнее, семейство функций) можно сделать самому. Например:
RO>Здесь более в духе STL было бы что-то вроде join_iterator. Тоже можно самому написать.
Итератор нужен для доступа к последовательности. Вы какую последовательность имели в виду? Я имел в виду, что вместо:
DirPath + "\\" + FileName

было бы оптимальнее писать:
Concat(DirPath, "\\", FileName)

Насколько я знаю, компилятор C# делает такую замену автоматически.
Пётр Седов (ушёл с RSDN)
Re[4]: минусы STL
От: alnsn Великобритания http://nasonov.blogspot.com
Дата: 07.09.06 08:07
Оценка:
Здравствуйте, gid_vvp, Вы писали:

A_A>>Может boost::lexical_cast + performance upgrade
Автор: korzhik
Дата: 02.09.06
?


_>К сожалению это не STL...


http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1973.html
Re[4]: Проблемы STL-контейнеров
От: kan Великобритания  
Дата: 07.09.06 08:29
Оценка: :)
Пётр Седов wrote:

> То, что для пустого vector-а эти два итератора равны, проблем создавать

> не должно.
> Можно использовать boost::optional<iterator>, но лучше не усложнять.
Не понял, а чем std::vector<X>::const_iterator() не устраивает?

> Я предупредил начинающих, что под size_t грабли лежат. А уж использовать

> его или нет — пускай сами решают.
Тут проблема не в STL, а знании начинающими арифметики и понимании операции вычитания. Всегда можно a == b — 1
преобразовать в a + 1 == b

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

> константное время.

> проигрывает ручному списку в стиле языка C:

Это называется intrusive containers

> это обобщение указателя, поэтому также может понадобиться снять

> константность с итератора. Не обязательно const_cast-ом, можно методом:
+

> А>>8. vector

> А>>8.1. Странное название у этого контейнера.
> RO>если еще учесть std::tr1::array = boost::array, то вообще весело станет
> RO>Это не так важно, все привыкли уже.
> + valarray
> Да, все привыкли к ещё одной программистской традиции, идущей вразрез с
> математикой. Одно из самых сложных действий в программировании —
> придумать хорошее имя для сущности. 'vector' — неудачное имя для
> динамического массива. Но это разговор для другого форума.
Я уже объяснял. Array — общее название массивов. Одномерный array называется vector (вспомни алгебру — там есть даже два
понятия — вектор-столбец и вектор-строка), двумерный — matrix. В STL vector есть одномерный array, так что наоборот, всё
очень точно.

> DirPath + "\\" + FileName

>
>
> было бы оптимальнее писать:
>
> Concat(DirPath, "\\", FileName)
Но в С++ нет функций с переменным числом аргументов (ellipsis не считается). И ради одной только операции конкатенации
вводить такую штуку?..

> Насколько я знаю, компилятор C# делает такую замену автоматически.

Хаки? Или как там это работает?
Posted via RSDN NNTP Server 2.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[3]: Проблемы STL-контейнеров
От: kan Великобритания  
Дата: 07.09.06 08:36
Оценка:
Alex_Avr wrote:

> Гм, а почему нельзя просто использовать list::front () и list::back ()?

> Мне кажется, что операции получения итераторов на элементы и вставка
> разделены не
> просто так, а для того, чтобы можно было гарантировать предсказуемое
> состояние
> программы в случае, если итератор является классом и при его копировании
> может вылететь исключение:
>
>
> class A { int n; };
>
> list<A> list_of_A;
> list<A>::iterator it = list_of_A.push_back (A ()); // если здесь вылетело исключение о нехватки памяти, как
> // определить произошло это при вставке элемента в контейнер
> // или при копировании итератора? И в каком состоянии
> // теперь находится list_of_A, был ли в него добавлен
> // элемент?
А как же std::map::insert, возвращающий аж pair? Как-то непоследовательно получается... Уж проще оператор копирования
итератора no-throw сделать, тем более это возможно (по крайней мере для всех стандартных контейнеров).
Posted via RSDN NNTP Server 2.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.