Подтвердить что ты не робот

Какие классы строк использовать в С++?

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

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

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

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

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

EDIT: Мы не используем unicode на данный момент, и маловероятно, что нам это понадобится. Однако, если есть что-то легко поддерживающее юникод (не ценой ICU...), это будет плюсом.

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

EDIT: Кроме того, если вы используете вызовы WIN32 api, конверсии CString будут проще.

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

EDIT: я использовал 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;
}

Ответ 3

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

Тем не менее, вы можете использовать ту же технику, что и MSLC9 SP1 STL, то есть "swaptimization", которая является самой смешной именованной оптимизацией.

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

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