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


184

Є клас C ++ Standard Template Library , яка забезпечує ефективну струнную функціональність конкатенації, схожу на С # StringBuilder або в Java StringBuffer ?


3
коротка відповідь: Так, у STL є клас для цього і він є std::ostringstream.
CoffeDeveloper

Гей @andrew Чи можете ви змінити прийняту відповідь? Є чітка переможна відповідь, і це не поточно прийнята відповідь.
null

Відповіді:


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, це було давно. Якщо ви скористаєтеся такою стратегією, спочатку подайте заявку.


13
Повторне винахід колеса. std :: stringstream - це правильна відповідь. Дивіться хороші відповіді нижче.
Kobor42

13
@ Kobor42 Я згоден з вами, коли я вказую на першому та останньому рядку своєї відповіді.
iain

1
Я не думаю, що scratchструна насправді нічого тут не виконує. Кількість перерозподілів основного рядка значною мірою буде функцією його остаточного розміру, а не кількості операцій додавання, якщо stringреалізація дійсно погана (тобто не використовує експоненціальне зростання). Тож "збір" вгору appendне допомагає, оскільки коли основна stringвеличина, вона зростатиме лише зрідка в будь-якому випадку. Крім того, він додає купу надмірних операцій копіювання, і може зробити більше перерозподілів (отже, дзвінки в new/ delete), оскільки ви додаєте до короткого рядка.
BeeOnRope

@BeeOnRope Я згоден з вами.
177

Я впевнений, str.reserve(1024);що буде швидше, ніж ця річ
hanshenrik

160

Способом C ++ було б використання std :: stringstream або просто об'єднання рядків. Структури C ++ є змінними, тому міркування щодо ефективності конкатенації не викликають особливих проблем.

що стосується форматування, ви можете зробити все те саме форматування в потоці, але по-іншому, аналогічноcout . або ви можете використовувати сильно набраний функтор, який інкапсулює це і надає інтерфейс типу String.Format, наприклад, boost :: формат


59
Рядки C ++ змінні : точно. Вся причина StringBuilderполягає в тому, щоб покрити неефективність незмінного базового типу String Java . Іншими словами, StringBuilderце печворк, тому ми повинні бути раді, що нам не потрібен такий клас на C ++.
bobobobo

57
@bobobobo незмінні струни мають і інші переваги, але коні для курсів
jk.

8
Чи не рядкові рядкові конкатенації створюють новий об'єкт, тому така ж проблема, як і з незмінністю на Java? Розглянемо, що всі змінні є рядками в наступному прикладі: a = b + c + d + e + f; Чи не буде виклик оператора + на b і c, потім оператора + на результат і d тощо?
Серж Рогач

9
Зачекайте людину хвилини, стандартний клас струн знає, як мутувати себе, але це не означає, що неефективності немає. Наскільки я знаю, std :: string не може просто збільшити розмір свого внутрішнього символу *. Це означає, що його мутуватимуть таким чином, що потребує більше символів, потрібно перерозподіл та копіювання. Він не відрізняється від вектора символів, і, безумовно, краще зарезервувати потрібний простір у такому випадку.
Trygve Skogsholm

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();


13

Ви можете використовувати .append () для просто об'єднання рядків.

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

Я думаю, ви навіть зможете зробити:

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

Що стосується операцій з форматування C # 's StringBuilder, я вважаю, snprintf(або sprintfякщо ви хочете ризикувати написанням баггі-коду ;-)) в масив символів і перетворення назад у рядок - це єдиний варіант.


Не так само, як printf або .NET's String.Format, чи не так?
Енді Шеллам

1
це трохи нечесно, щоб сказати, що вони єдиний спосіб
jk.

2
@jk - вони є єдиним способом порівняння здатності форматування StringBuilder .NET, про що конкретно задали оригінальне запитання. Я сказав "я вважаю", тому я можу помилитися, але чи можете ви показати мені спосіб отримати функціональність StringBuilder в C ++ без використання printf?
Енді Шеллам

оновив мою відповідь, щоб включити кілька альтернативних варіантів форматування
jk.

6

Оскільки std::stringв C ++ є змінним, ви можете використовувати це. Він має += operatorі appendфункцію.

Якщо вам потрібно додати числові дані, скористайтеся std::to_stringфункціями.

Якщо ви хочете ще більше гнучкості у вигляді можливості серіалізувати будь-який об’єкт до рядка, тоді використовуйте std::stringstreamклас. Але вам потрібно буде реалізувати власні функції оператора потокового передавання, щоб він працював із власними спеціальними класами.


4

std :: string's + = не працює з const char * (такі речі, як "рядок для додавання", здаються), тому, безумовно, використання stringstream є найбільш близьким до того, що потрібно - ви просто використовуєте << замість +


3

Зручний конструктор струн для c ++

Як і багато людей, які раніше відповідали, std :: stringstream є методом вибору. Він працює добре і має багато варіантів перетворення та форматування. IMO у нього є один досить незручний недолік: Ви не можете використовувати його як один вкладиш або як вираз. Ви завжди повинні написати:

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

що дуже дратує, особливо коли ви хочете ініціалізувати рядки в конструкторі.

Причина полягає в тому, що a) std :: stringstream не має оператора перетворення в std :: string та b) оператора << () поточного рядка не повертає посилання stringstream, а замість 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 також)


Це акуратно. Я не бачу, чому 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

Це вже фіксує найбільше вузьке місце, тоді як набирати декілька відсотків у швидкості при впровадженні міни не варто недоліків.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.