Какие классы строк использовать в C ++? - PullRequest
17 голосов
/ 17 января 2011

у нас есть многопоточное настольное приложение на C ++ (MFC). В настоящее время разработчики используют либо CString, либо std :: string, вероятно, в зависимости от их настроения. Поэтому мы хотели бы выбрать одну реализацию (возможно, отличную от этих двух).

CString MFC основан на идиоме копирование при записи (COW), и некоторые люди утверждают, что это недопустимо в многопоточной среде (и, вероятно, ссылка на эта статья ). Я не убежден в таких утверждениях, поскольку атомные счетчики кажутся довольно быстрыми, а также эти накладные расходы каким-то образом компенсируются сокращением перераспределения памяти.

Я узнал, что реализация std :: string зависит от компилятора - это не COW в MSVC, но он есть или был в gcc. Насколько я понял, новый стандарт C ++ 0x исправит это, потребовав реализации без COW и решив некоторые другие проблемы, такие как непрерывные требования к буферу. Так что на самом деле std :: string на данный момент выглядит не очень хорошо определенным ...

Быстрый пример того, что мне не нравится в std :: string: нет способа вернуть строку из функции без чрезмерного перераспределения (конструктор копирования при возврате по значению и отсутствие доступа к внутреннему буферу для его оптимизации) поэтому «возврат по ссылке», например, std::string& Result не помогает). Я могу сделать это с CString, либо возвращая по значению (без копирования из-за COW), либо передавая по ссылке и получая прямой доступ к буферу. Опять же, C ++ 0x на помощь с его ссылками на rvalue, но у нас не будет C ++ 0x в ближайшей функции.

Какой класс строк мы должны использовать? Может ли COW действительно стать проблемой? Существуют ли другие часто используемые эффективные реализации строк? Спасибо.

РЕДАКТИРОВАТЬ: Мы не используем Unicode в настоящее время, и вряд ли нам это понадобится. Однако, если есть что-то легко поддерживающее юникод (не за счет ICU ...), это было бы плюсом.

Ответы [ 7 ]

16 голосов
/ 17 января 2011

Я бы использовал std::string.

  • Содействие отделению от MFC
  • Лучшее взаимодействие с существующими библиотеками C ++

"Возвращаемое значение"проблема в основном не проблема.Компиляторы очень хороши в выполнении Оптимизации возвращаемого значения (RVO), что фактически устраняет копию в большинстве случаев при возврате по значению.Если этого не произойдет, вы обычно можете настроить функцию.

COW отклонено по причине: она не масштабируется (хорошо) и столь ожидаемое увеличениескорость действительно не измерялась (см. статью Херба Саттера ).Атомные операции не так дешевы, как кажутся.С одноядерным моно-процессором это было легко, но теперь многоядерные являются товаром, а многопроцессорные широко доступны (для серверов).В таких распределенных архитектурах есть несколько кэшей, которые необходимо синхронизировать, и чем более распределенной является архитектура, тем дороже атомарные операции.

Реализует ли CString Оптимизация малых строк ?Это простой трюк, который позволяет строке не выделять память для маленьких строк (обычно несколько символов).Очень полезно, потому что оказывается, что большинство строк на самом деле маленькие, сколько строк в вашем приложении меньше 8 символов?

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

5 голосов
/ 17 января 2011

На самом деле, ответ может быть «Это зависит».Но, если вы используете MFC, IMHO, использование CString было бы лучше.Также вы можете использовать CString с контейнерами STL.Но, это приведет к другому вопросу, я должен использовать контейнеры stl или контейнеры MFC с CString?Использование CString обеспечит гибкость вашего приложения, например, при конвертации в Юникод.

EDIT: Более того, если вы используете API-вызовы WIN32, преобразования CString будут проще.

EDIT: CString имеет GetBuffer() и относительно методов, которые позволяют вам напрямую изменять буфер.

РЕДАКТИРОВАТЬ: я использовал CString в нашей оболочке SQLite, и форматирование CString стало проще.

    bool RS::getString(int idx, CString& a_value) {

//bla bla

        if(getDB()->getEncoding() == IDatabase::UTF8){
            a_value.Format(_T("%s"), sqlite3_column_text(getCommand()->getStatement(), idx));
        }else{
            a_value.Format(_T("%s"), sqlite3_column_text16(getCommand()->getStatement(), idx));
        }
        return true;
}
1 голос
/ 17 января 2011

Я говорю всегда иди std::string.Как уже упоминалось, RVO и NVRO сделают возврат копий дешевым, и когда вы в конечном итоге переключитесь на C ++ 0x, вы получите хороший прирост производительности из семантики перемещения, ничего не делая.Если вы хотите взять какой-либо код и использовать его в не-ATL / MFC проекте, вы не можете использовать CString, но std::string будет там, так что вам будет намного легче.Наконец, вы упомянули в комментарии, что вы используете контейнеры STL вместо контейнеров MFC (хороший ход).Почему бы не остаться последовательным и использовать строку STL тоже?

1 голос
/ 17 января 2011

Я бы предложил принять решение "по DLL".Если у вас есть библиотеки DLL, сильно зависящие от MFC (например, вашего уровня GUI), где вам нужно много вызовов MFC с параметрами CString, используйте CString.Если у вас есть библиотеки DLL, в которых единственной вещью из MFC, которую вы собираетесь использовать, будет класс CString, используйте вместо этого std::string.Конечно, вам понадобится функция преобразования между обоими классами, но я подозреваю, что вы уже решили эту проблему.

1 голос
/ 17 января 2011

Я не знаю других распространенных реализаций строк - все они страдают от одинаковых языковых ограничений в C ++ 03.Либо они предлагают что-то конкретное, например, то, как компоненты ICU хороши для Unicode, они действительно старые, как CString, или std :: string превосходит их.

Однако вы можете использовать ту же технику, что и MSVC9SP1 STL использует, т. Е. «Swaptimization», которая является самой остроумной из когда-либо названных оптимизаций.

void func(std::string& ref) {
    std::string retval;
    // ...
    std::swap(ref, retval); // No copying done here.
}

Если вы свернули пользовательский класс строки, который ничего не выделял в его конструкторе по умолчанию (или проверилРеализация STL), затем его замена гарантировала бы отсутствие избыточных выделений.Например, мой MSVC STL использует SSO и по умолчанию не выделяет какую-либо кучу памяти, поэтому, заменяя вышесказанное выше, я не получаю избыточных выделений.

Вы также можете существенно повысить производительность, просто не используя дорогостоящее выделение кучи,Существуют распределители, предназначенные для временного размещения, и вы можете заменить распределитель, используемый в вашей любимой реализации STL, на собственный.Вы можете получить такие вещи, как пулы объектов из Boost или запустить арену памяти.Вы можете получить в десять раз лучшую производительность по сравнению с обычным новым распределением.

0 голосов
/ 17 января 2011

std::string обычно считается подсчетом ссылок, поэтому передача по значению все еще является дешевой операцией (и даже более того, с использованием ссылки на rvalue в C ++ 0x).COW запускается только для строк, на которые указывают несколько ссылок, т. Е.

std::string foo("foo");
std::string bar(foo);
foo[0] = 'm';

будет проходить путь COW.Поскольку COW происходит внутри operator[], вы можете заставить строку использовать закрытый буфер, используя ее (неконстантные) operator[]() или begin() методы.

0 голосов
/ 17 января 2011

Я бы посоветовал использовать std :: basic_string в качестве основной базы шаблонов строк, если нет веской причины поступить иначе. Я говорю basic_string, потому что если вы обрабатываете 16-битные символы, вы бы использовали wstring.

Если вы собираетесь использовать TCHAR, вы, вероятно, должны определить tstring как basic_string и, возможно, захотите реализовать класс признаков для него, чтобы использовать такие функции, как _tcslen и т. Д.

...