С++ эквивалент StringBuffer/StringBuilder?

Существует ли класс стандартной библиотеки шаблонов С++, который обеспечивает эффективную функциональность конкатенации строк, аналогичную С# StringBuilder или Java StringBuffer?

Ответ 1

ПРИМЕЧАНИЕ. Этот ответ получил некоторое внимание в последнее время. Я не выступаю за это как решение (это решение, которое я видел в прошлом, перед STL). Это интересный подход и должен применяться только к std::string или std::stringstream, если после профилирования вашего кода вы обнаружите, что это улучшает.

Обычно я использую std::string или std::stringstream. У меня никогда не было проблем с этим. Обычно я резервирую комнату, если я заранее знаю приблизительный размер строки.

Я видел, как другие люди создают свой оптимизированный строковый построитель в далеком прошлом.

class StringBuilder {
private:
    std::string main;
    std::string scratch;

    const std::string::size_type ScratchSize = 1024;  // or some other arbitrary number

public:
    StringBuilder & append(const std::string & str) {
        scratch.append(str);
        if (scratch.size() > ScratchSize) {
            main.append(scratch);
            scratch.resize(0);
        }
        return *this;
    }

    const std::string & str() {
        if (scratch.size() > 0) {
            main.append(scratch);
            scratch.resize(0);
        }
        return main;
    }
};

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

Мне не нужен этот трюк с std::string или std::stringstream. Я думаю, что он использовался с третьей библиотекой строк до std::string, это было так давно. Если вы сначала примете стратегию, как этот профиль, свое приложение.

Ответ 2

Путь на С++ будет использовать std:: stringstream или просто простые конкатенации строк. Строки С++ являются изменяемыми, поэтому соображения производительности конкатенации менее опасны.

в отношении форматирования вы можете сделать все то же форматирование в потоке, но по-другому, подобно cout. или вы можете использовать строго типизированный функтор, который инкапсулирует это и предоставляет интерфейс String.Format, например. boost:: format

Ответ 3

Функция std::string.append не является хорошим вариантом, поскольку она не принимает многие формы данных. Более полезной альтернативой является использование std: stringstream, например:

#include <sstream>
// ...

std::stringstream ss;

//put arbitrary formatted data into the stream
ss << 4.5 << ", " << 4 << " whatever";

//convert the stream buffer into a string
std::string str = ss.str();

Ответ 4

std::string - эквивалент С++: он изменен.

Ответ 5

Вы можете использовать .append() для просто конкатенации строк.

std::string s = "string1";
s.append("string2");

Я думаю, что вы даже можете это сделать:

std::string s = "string1";
s += "string2";

Что касается операций форматирования С# StringBuilder, я полагаю, что snprintf (или sprintf, если вы хотите рискнуть записать багги-код;-)) в массив символов и преобразовать обратно в строку, это единственное вариант.

Ответ 6

Так как std::string в С++ изменен, вы можете использовать это. Он имеет функцию += operator и append.

Если вам нужно добавить числовые данные, используйте функции std::to_string.

Если вы хотите еще большую гибкость в виде возможности сериализации любого объекта для строки, используйте класс std::stringstream. Но вам нужно будет реализовать собственные функции оператора потоковой передачи, чтобы он работал с вашими собственными классами.

Ответ 7

std::string + = не работает с const char * (что может показаться, например, как "строка для добавления" ), поэтому определенно использование stringstream является самым близким к тому, что требуется - вы просто используете < < < < < < <; вместо +

Ответ 8

Контент Rope может стоить, если нужно вставить/удалить строку в случайное место целевой строки или для длинного char последовательностей. Вот пример реализации SGI:

crope r(1000000, 'x');          // crope is rope<char>. wrope is rope<wchar_t>
                                // Builds a rope containing a million 'x's.
                                // Takes much less than a MB, since the
                                // different pieces are shared.
crope r2 = r + "abc" + r;       // concatenation; takes on the order of 100s
                                // of machine instructions; fast
crope r3 = r2.substr(1000000, 3);       // yields "abc"; fast.
crope r4 = r2.substr(1000000, 1000000); // also fast.
reverse(r2.mutable_begin(), r2.mutable_end());
                                // correct, but slow; may take a
                                // minute or more.

Ответ 9

Я хотел добавить что-то новое из-за следующего:

В первый момент я не смог победить

std::ostringstream operator<<

но с большим количеством попыток я смог сделать StringBuilder быстрее в некоторых случаях.

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

Реальный путь, который я, наконец, внедрил (Horror!), - использовать непрозрачный буфер (std::vector <char> ):

  • 1 байтовый заголовок (2 бита, чтобы указать, следующие ли следующие данные: перемещенная строка, строка или байт [])
  • 6 бит, чтобы рассчитать длину байта []

для байта []

  • Я сохраняю непосредственно байты коротких строк (для доступа к последовательной памяти)

для перемещенных строк (строки, добавленные с помощью std::move)

  • Указатель на объект std::string (у нас есть собственность)
  • установить флаг в классе, если там используются неиспользуемые зарезервированные байты.

для строк

  • Указатель на объект std::string (без права собственности)

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

Это было, наконец, немного быстрее, чем std::ostringstream, но оно имеет несколько недостатков:

  • Я предположил фиксированную длину char типов (так что 1,2 или 4 байта, не очень хорошо для UTF8), я не говорю, что это не сработает для UTF8. Просто я не проверял его на лень.
  • Я использовал плохую практику кодирования (непрозрачный буфер, легко сделать его не переносным, я считаю, что мой переносимый кстати)
  • Недостатки всех функций ostringstream
  • Если какая-либо ссылочная строка удаляется до слияния всех строк: undefined.

вывод? использование std::ostringstream

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

Ответ 10

Удобный построитель строк для С++

Как и многие ранее ответившие, std:: stringstream - это метод выбора. Он работает хорошо и имеет множество вариантов преобразования и форматирования. IMO у него есть один довольно неудобный недостаток, хотя: вы не можете использовать его как один лайнер или как выражение. Вам всегда нужно писать:

std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );

что очень раздражает, особенно если вы хотите инициализировать строки в конструкторе.

Причина в том, что a) std:: stringstream не имеет оператора преобразования для std::string и b) оператора <() строкового потока не возвращают ссылку на строковый поток, а вместо этого ссылаются на ссылку std:: ostream, которая не может быть далее вычислена как поток строк.

Решение состоит в том, чтобы переопределить std:: stringstream и дать ему лучшие совпадающие операторы:

namespace NsStringBuilder {
template<typename T> class basic_stringstream : public std::basic_stringstream<T>
{
public:
    basic_stringstream() {}

    operator const std::basic_string<T> () const                                { return std::basic_stringstream<T>::str();                     }
    basic_stringstream<T>& operator<<   (bool _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (char _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (signed char _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned char _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (short _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned short _val)                   { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (int _val)                              { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned int _val)                     { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long long _val)                        { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long long _val)               { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (float _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (double _val)                           { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long double _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (void* _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::streambuf* _val)                  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ostream& (*_val)(std::ostream&))  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios& (*_val)(std::ios&))          { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios_base& (*_val)(std::ios_base&)){ std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (const T* _val)                         { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val)); }
    basic_stringstream<T>& operator<<   (const std::basic_string<T>& _val)      { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val.c_str())); }
};

typedef basic_stringstream<char>        stringstream;
typedef basic_stringstream<wchar_t>     wstringstream;
}

С этим вы можете писать такие вещи, как

std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )

даже в конструкторе.

Я должен признаться, что не оценил производительность, поскольку я не использовал ее в среде, которая еще больше использует строковое построение, но я предполагаю, что это будет не намного хуже, чем std:: stringstream, поскольку все делается через ссылки (кроме преобразования в строку, но это также операция копирования в std:: stringstream)