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

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


person An̲̳̳drew    schedule 17.03.2010    source источник
comment
короткий ответ: Да, у STL есть класс для этого, и это std::ostringstream.   -  person CoffeDeveloper    schedule 30.04.2015
comment
Привет @andrew. Не могли бы вы изменить принятый ответ? Есть явный выигрышный ответ, и это не текущий принятый ответ.   -  person null    schedule 17.03.2020
comment
@null это было сделано!   -  person An̲̳̳drew    schedule 05.01.2021


Ответы (10)


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

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

person jk.    schedule 17.03.2010
comment
Строки C++ изменяемы: точно. Вся причина существования StringBuilder заключается в том, чтобы скрыть неэффективность неизменного базового типа String в Java . Другими словами, StringBuilder — это лоскутное одеяло, поэтому мы должны радоваться, что нам не нужен такой класс в C++. - person bobobobo; 16.04.2013
comment
Однако у неизменяемых строк @bobobobo есть и другие преимущества, это лошади для курсов - person jk.; 16.04.2013
comment
Разве простые конкатенации строк не создают новый объект, так что та же проблема, что и с неизменностью в Java? Рассмотрим все переменные в виде строк в следующем примере: a = b + c + d + e + f; Разве он не будет вызывать оператор + для b и c, затем оператор + для результата и d и т. д.? - person Serge Rogatch; 24.06.2015
comment
@Hoten, насколько я понимаю, семантика перемещения сохранит копирование объекта только при возврате из operator+. Однако внутри operator+ по-прежнему необходимо создать новый строковый объект для хранения результата конкатенации строк. - person Serge Rogatch; 30.09.2015
comment
@jk Я считаю, что пометка std::string как const делает его неизменным, поэтому я бы назвал это гибкостью. - person Jan Smrčina; 24.02.2016
comment
@SergeRogatch Я видел, как компилятор Java избегал создания нескольких объектов в стеке таких конкатенаций, создавая StringBuilder, используя append (), а затем получая результат в конце с помощью toString (). - person neuralmer; 21.06.2016
comment
Подождите минуту, люди, стандартный строковый класс знает, как мутировать себя, но это не значит, что неэффективности нет. Насколько я знаю, std::string не может просто увеличить размер своего внутреннего char*. Это означает, что изменение его таким образом, что требуется больше символов, требует перераспределения и копирования. Это ничем не отличается от вектора символов, и в этом случае, безусловно, лучше зарезервировать необходимое пространство. - person Trygve Skogsholm; 31.07.2016
comment
@TrygveSkogsholm - это ничем не отличается от вектора символов, но, конечно, емкость строки может быть больше, чем ее размер, поэтому не все добавления требуют перераспределения. В общем, строки будут использовать стратегию экспоненциального роста, поэтому добавление по-прежнему амортизирует операцию линейной стоимости. Это отличается от неизменяемых строк Java, в которых каждая операция добавления должна копировать все символы в обеих строках в новую, поэтому серия добавлений обычно заканчивается как O(n). - person BeeOnRope; 18.11.2017

Функция 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();
person Stu    schedule 29.09.2011

ПРИМЕЧАНИЕ: этот ответ недавно привлек внимание. Я не пропагандирую это как решение (это решение я видел в прошлом, до 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, это было так давно. Если вы примете такую ​​стратегию, сначала профилируйте свое приложение.

person iain    schedule 17.03.2010
comment
Новое изобретение колеса. std::stringstream - правильный ответ. Смотрите хорошие ответы ниже. - person Kobor42; 16.04.2013
comment
@ Kobor42 Я согласен с вами, поскольку я указываю на первую и последнюю строку моего ответа. - person iain; 16.04.2013
comment
Я не думаю, что строка scratch действительно что-то здесь делает. Количество перераспределений основной строки в значительной степени будет зависеть от ее конечного размера, а не от количества операций добавления, если только реализация string не будет действительно плохой (т. е. не использует экспоненциальный рост). Таким образом, пакетирование append не помогает, потому что, как только базовый string станет большим, он будет только время от времени расти в любом случае. Кроме того, он добавляет кучу избыточных операций копирования и может больше перераспределять (следовательно, вызовы new/delete), так как вы добавляете короткую строку. - person BeeOnRope; 18.11.2017
comment
@BeeOnRope Я согласен с тобой. - person iain; 20.11.2017
comment
я почти уверен, что str.reserve(1024); будет быстрее, чем эта штука - person hanshenrik; 25.04.2019

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

person dan04    schedule 09.06.2010

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

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

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

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

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

person Andy Shellam    schedule 17.03.2010
comment
Но не так, как printf или .NET String.Format, не так ли? - person Andy Shellam; 17.03.2010
comment
немного неискренне говорить, что это единственный способ, хотя - person jk.; 17.03.2010
comment
@jk - это единственный способ сравнить возможности форматирования .NET StringBuilder, о чем конкретно задавался исходный вопрос. Я сказал, что верю, поэтому могу ошибаться, но можете ли вы показать мне способ получить функциональность StringBuilder в C++ без использования printf? - person Andy Shellam; 17.03.2010
comment
обновил мой ответ, включив в него некоторые альтернативные параметры форматирования - person jk.; 22.03.2010

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

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

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

person Daemin    schedule 29.09.2011

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

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

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

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

Причина в том, что а) std::stringstream не имеет оператора преобразования в std::string и б) оператор ‹‹() строкового потока не возвращает ссылку на строковый поток, а вместо этого возвращает ссылку на 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)

person user2328447    schedule 02.11.2017
comment
Это аккуратно. Не понимаю, почему std::stringstream так себя не ведет. - person einpoklum; 07.06.2020

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

person sergeys    schedule 27.10.2011

Контейнер 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.
person Igor    schedule 24.01.2014

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

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

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
  • Если какая-то строка, на которую ссылаются, удалена до объединения всех строк: поведение undefined.

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

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

person CoffeDeveloper    schedule 30.04.2015