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


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

10 135

10 ответов:

обратите внимание, что этот ответ получил некоторое внимание в последнее время. Я не выступаю за это как решение (это решение, которое я видел в прошлом, до 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, это было так давно. Если принять стратегию, как этот профиль вашего приложения.

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

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

строка std::.функция добавления не является хорошим вариантом, потому что она не принимает много форм данных. Более полезной альтернативой является использование 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();

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

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

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

Я думаю, вы могли бы даже быть в состоянии сделать:

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

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

С std::string в C++ является изменчивым вы можете использовать это. Он имеет += operator и

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

на трос контейнер может стоить, если нужно вставить/удалить строку в случайное место назначения строки или для длинных последовательностей символов. Вот пример из реализации 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.

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

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

std::ostringstream ' s operator<<

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

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

реальный способ, которым я, наконец, реализовал его (ужас!) заключается в использовании непрозрачного буфер (std:: vector ):

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

для Byte [ ]

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

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

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

для строк

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

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

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

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

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

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

удобный строитель строк для c++

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

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

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

причина это означает, что a) std::stringstream не имеет оператора преобразования в std::string и b) оператор

решение состоит в том, чтобы переопределить 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 )

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

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