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

184

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

Андрей
источник
3
краткий ответ: да, у STL есть класс для этого, и это так std::ostringstream.
CoffeDeveloper
Привет, @ Андрей. Можете ли вы изменить принятый ответ? Существует четкий выигрышный ответ, и это не текущий принятый ответ.
ноль

Ответы:

53

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

Iain
источник
13
Изобретая колесо. std :: stringstream - правильный ответ. Смотрите хорошие ответы ниже.
Kobor42
13
@ Kobor42 Я согласен с вами, поскольку я указываю на первую и последнюю строку моего ответа.
13
1
Я не думаю, что scratchстрока действительно чего-то добивается здесь. Количество перераспределений основной строки в значительной степени будет зависеть от ее окончательного размера, а не от количества операций добавления, если stringреализация не очень плохая (т. Е. Не использует экспоненциальный рост). Таким образом, «группировка» appendне помогает, потому что, если базовый размер stringбольшой, он будет расти только изредка в любом случае. Кроме того, он добавляет кучу избыточных операций копирования и может перераспределять больше (следовательно, вызывает new/ delete), так как вы добавляете короткую строку.
BeeOnRope
@BeeOnRope Я согласен с вами.
17
Я уверен, str.reserve(1024);что будет быстрее, чем эта вещь
Hanshenrik
160

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

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

JK.
источник
59
Строки C ++ являются изменяемыми : точно. Единственная причина StringBuilderзаключается в том, чтобы покрыть неэффективность неизменяемого базового типа String в Java . Другими словами, StringBuilderэто лоскутное одеяло, поэтому мы должны быть рады, что нам не нужен такой класс в C ++.
Бобобобо
57
У неизменяемых строк @bobobobo есть и другие преимущества: лошади для курсов
jk.
8
Разве простые конкатенации строк не создают новый объект, поэтому проблема та же, что и с неизменяемостью в Java? Рассмотрим все переменные как строки в следующем примере: a = b + c + d + e + f; Разве это не будет вызывать оператор + для b и c, затем оператор + для результата и d и т. Д.?
Серж Рогач
9
Подожди минутку, люди, класс стандартных строк знает, как мутировать себя, но это не значит, что неэффективности нет. Насколько я знаю, std :: string не может просто увеличить размер своего внутреннего символа *. Это означает, что изменение его таким образом, что требуется больше символов, требует перераспределения и копирования. Он ничем не отличается от вектора символов, и в этом случае лучше зарезервировать необходимое место.
Трюгве Скогсхольм
7
@TrygveSkogsholm - он ничем не отличается от вектора символов, но, конечно, «емкость» строки может быть больше ее размера, поэтому не все добавления нуждаются в перераспределении. В общем случае в строках будет использоваться стратегия экспоненциального роста, поэтому добавление по-прежнему амортизирует операцию с линейными затратами. Это отличается от неизменяемых строк Java, в которых каждая операция добавления должна копировать все символы в обеих строках в новую, поэтому последовательность добавления заканчивается, как O(n)в общем случае.
BeeOnRope
93

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();
Stu
источник
43

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

dan04
источник
13

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

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

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

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

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

Энди Шеллам
источник
Не так ли как в printf или .NET String.Format, правда?
Энди Шеллам
1
немного неискренне говорить, что они - единственный путь, хотя
JK.
2
@jk - это единственный способ сравнить форматирующую способность .NET StringBuilder, как и было задано в оригинальном вопросе. Я сказал «я верю», поэтому я могу ошибаться, но можете ли вы показать мне способ получить функциональность StringBuilder в C ++ без использования printf?
Энди Шеллам
обновил мой ответ, включив некоторые альтернативные варианты форматирования
jk.
6

Поскольку std::stringв C ++ изменчиво, вы можете использовать это. Это имеет += operatorиappend функция.

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

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

Daemin
источник
4

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

Sergeys
источник
3

Удобный конструктор строк для C ++

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

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)

user2328447
источник
Это аккуратно. Я не понимаю, почему std::stringstreamтак себя не ведет.
einpoklum
1

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

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

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

std::ostringstream «s operator<<

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

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

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

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

для байта []

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

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

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

для струнных

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

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

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

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

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

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

CoffeDeveloper
источник