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.

Пётр Седов
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.