C ++ setara dengan StringBuffer / StringBuilder?

184

Apakah ada kelas C ++ Standard Template Library yang menyediakan serangkaian fungsi Rangkaian efisien, mirip dengan C # 's StringBuilder atau Jawa StringBuffer ?

An̲̳̳drew
sumber
3
jawaban singkatnya adalah: Ya, STL memiliki kelas untuk itu dan itu std::ostringstream.
CoffeDeveloper
Hai @ andrew. Bisakah Anda mengubah jawaban yang diterima? Ada jawaban yang jelas dan bukan jawaban yang diterima saat ini.
null

Jawaban:

53

Perhatikan jawaban ini baru-baru ini mendapat perhatian. Saya tidak menganjurkan ini sebagai solusi (ini adalah solusi yang saya lihat di masa lalu, sebelum STL). Ini adalah pendekatan yang menarik dan hanya boleh diterapkan di atas std::stringatau std::stringstreamjika setelah membuat profil kode Anda Anda menemukan ini membuat perbaikan.

Saya biasanya menggunakan salah satu std::stringatau std::stringstream. Saya tidak pernah punya masalah dengan ini. Saya biasanya akan memesan kamar terlebih dahulu jika saya tahu ukuran kasar tali sebelumnya.

Saya telah melihat orang lain membuat pembuat string yang dioptimalkan sendiri di masa lalu.

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;
    }
};

Ini menggunakan dua string satu untuk sebagian besar string dan yang lainnya sebagai area awal untuk merangkai string pendek. Ini mengoptimalkan penambahan dengan cara batching operasi penambahan pendek dalam satu string kecil kemudian menambahkan ini ke string utama, sehingga mengurangi jumlah realokasi yang diperlukan pada string utama saat menjadi lebih besar.

Saya belum meminta trik ini dengan std::stringatau std::stringstream. Saya pikir itu digunakan dengan perpustakaan string pihak ketiga sebelum std :: string, itu sudah lama sekali. Jika Anda mengadopsi strategi seperti profil ini, aplikasi Anda terlebih dahulu.

iain
sumber
13
Menemukan kembali roda. std :: stringstream adalah jawaban yang tepat. Lihat jawaban yang bagus di bawah ini.
Kobor42
13
@ Kobor42 Saya setuju dengan Anda ketika saya menunjukkan pada baris pertama dan terakhir dari jawaban saya.
iain
1
Saya tidak berpikir scratchstring benar-benar menyelesaikan apa pun di sini. Jumlah realokasi string utama sebagian besar akan menjadi fungsi dari ukuran finalnya, bukan jumlah operasi tambahan, kecuali stringimplementasinya benar-benar buruk (yaitu, tidak menggunakan pertumbuhan eksponensial). Jadi, "menumpuk" appendtidak akan membantu karena begitu dasarnya stringitu besar itu hanya akan tumbuh sesekali. Selain itu, ia menambahkan banyak operasi penyalinan yang redundan, dan mungkin lebih banyak realokasi (karena itu panggilan ke new/ delete) karena Anda menambahkan string pendek.
BeeOnRope
@BeeOnRope Saya setuju dengan Anda.
iain
Saya cukup yakin str.reserve(1024);akan lebih cepat dari hal ini
hanshenrik
160

Cara C ++ adalah dengan menggunakan std :: stringstream atau hanya gabungan string sederhana. String C ++ bisa berubah sehingga pertimbangan kinerja gabungan tidak terlalu menjadi perhatian.

berkenaan dengan pemformatan, Anda dapat melakukan semua pemformatan yang sama pada aliran, tetapi dengan cara yang berbeda, mirip dengancout . atau Anda dapat menggunakan functor yang sangat diketik yang merangkum ini dan menyediakan antarmuka seperti String.Format misalnya boost :: format

jk.
sumber
59
String C ++ bisa berubah : persis. Seluruh alasan yang StringBuilderada adalah untuk menutupi ketidakefisienan tipe String dasar Java yang tidak berubah . Dengan kata lain StringBuilderadalah tambal sulam, jadi kita harus senang kita tidak perlu kelas seperti itu di C ++.
bobobobo
57
@ stringobobobo abadi memiliki manfaat lain, kudanya untuk kursus
jk.
8
Jangan penggabungan string sederhana membuat objek baru, jadi masalah yang sama seperti dengan kekekalan di Jawa? Pertimbangkan semua variabel adalah string dalam contoh berikut: a = b + c + d + e + f; Bukankah itu akan memanggil operator + pada b dan c, lalu operator + pada hasilnya dan d, dll?
Serge Rogatch,
9
Tunggu sebentar, kelas string standar tahu cara bermutasi, tetapi itu tidak berarti inefisiensi tidak ada. Sejauh yang saya tahu std :: string tidak bisa begitu saja memperluas ukuran karakter internal *. Itu berarti memutasinya dengan cara yang membutuhkan lebih banyak karakter memerlukan realokasi dan menyalin. Ini tidak berbeda dari vektor karakter dan tentu saja lebih baik untuk memesan ruang yang Anda butuhkan dalam kasus itu.
Trygve Skogsholm
7
@TrygveSkogsholm - tidak berbeda dari vektor karakter, tetapi tentu saja "kapasitas" dari string dapat lebih besar dari ukurannya, jadi tidak semua append membutuhkan realokasi. Secara umum string akan menggunakan strategi pertumbuhan eksponensial sehingga menambahkan masih diamortisasi ke operasi biaya linier. Itu berbeda dari String Java yang tidak berubah di mana setiap operasi append perlu menyalin semua karakter di kedua String ke yang baru, sehingga serangkaian penambahan berakhir seperti O(n)pada umumnya.
BeeOnRope
93

The std::string.appendfungsi bukan pilihan yang baik karena tidak menerima berbagai bentuk data. Alternatif yang lebih bermanfaat adalah menggunakan std::stringstream; seperti itu:

#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
sumber
43

std::string adalah setara C ++: Ini bisa berubah.

dan04
sumber
13

Anda dapat menggunakan .append () untuk string yang digabungkan.

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

Saya pikir Anda bahkan dapat melakukannya:

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

Adapun operasi pemformatan C # StringBuilder, saya percaya snprintf(atau sprintfjika Anda ingin mengambil risiko menulis kode kereta ;-)) ke dalam array karakter dan mengkonversi kembali ke string adalah tentang satu-satunya pilihan.

Andy Shellam
sumber
Tidak dengan cara yang sama seperti printf atau .NET's String. Namun, bukan?
Andy Shellam
1
itu agak tidak jujur ​​untuk mengatakan bahwa mereka adalah satu-satunya cara
jk.
2
@ jk - mereka satu-satunya cara ketika membandingkan kemampuan format .NET's StringBuilder, yang merupakan pertanyaan awal yang diajukan secara spesifik. Saya memang mengatakan "Saya percaya" sehingga saya bisa salah, tetapi bisakah Anda menunjukkan kepada saya cara untuk mendapatkan fungsionalitas StringBuilder di C ++ tanpa menggunakan printf?
Andy Shellam
memperbarui jawaban saya untuk menyertakan beberapa opsi format alternatif
jk.
6

Karena std::stringdalam C ++ bisa berubah, Anda dapat menggunakannya. Ini memiliki += operatordan appendfungsi.

Jika Anda perlu menambahkan data numerik gunakan std::to_stringfungsi.

Jika Anda menginginkan fleksibilitas yang lebih besar dalam bentuk mampu membuat serialisasi objek apa pun menjadi string, gunakan std::stringstreamkelas. Tetapi Anda harus mengimplementasikan fungsi operator streaming Anda sendiri agar dapat bekerja dengan kelas kustom Anda sendiri.

Daemin
sumber
4

std :: string's + = tidak berfungsi dengan const char * (apa yang tampak seperti "string untuk ditambahkan" tampaknya), jadi pasti menggunakan stringstream adalah yang paling dekat dengan apa yang diperlukan - Anda hanya menggunakan << bukannya +

sergeys
sumber
3

Pembuat string yang nyaman untuk c ++

Seperti banyak orang yang menjawab sebelumnya, std :: stringstream adalah metode pilihan. Ini berfungsi baik dan memiliki banyak opsi konversi dan pemformatan. IMO memiliki satu kelemahan yang cukup merepotkan: Anda tidak dapat menggunakannya sebagai satu liner atau sebagai ekspresi. Anda selalu harus menulis:

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

yang cukup menjengkelkan, terutama ketika Anda ingin menginisialisasi string dalam konstruktor.

Alasannya adalah, bahwa a) std :: stringstream tidak memiliki operator konversi ke std :: string dan b) operator dari stringstream tidak mengembalikan referensi stringstream, tetapi sebaliknya std :: ostream reference - yang tidak dapat dihitung lebih lanjut sebagai aliran string.

Solusinya adalah mengesampingkan std :: stringstream dan memberikannya operator pencocokan yang lebih baik:

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;
}

Dengan ini, Anda dapat menulis hal-hal seperti

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

bahkan di konstruktor.

Saya harus mengakui bahwa saya tidak mengukur kinerja, karena saya belum menggunakannya di lingkungan yang banyak menggunakan pembuatan string, tapi saya berasumsi itu tidak akan jauh lebih buruk daripada std :: stringstream, karena semuanya sudah selesai melalui referensi (kecuali konversi ke string, tapi itu operasi salinan di std :: stringstream juga)

pengguna2328447
sumber
Ini rapi. Saya tidak mengerti mengapa std::stringstreamtidak berperilaku seperti ini.
einpoklum
1

The Rope wadah mungkin layak jika harus memasukkan / menghapus string ke tempat acak string tujuan atau untuk urutan arang panjang. Berikut adalah contoh dari implementasi 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.
Igor
sumber
0

Saya ingin menambahkan sesuatu yang baru karena hal berikut:

Pada usaha pertama saya gagal mengalahkan

std::ostringstream ini operator<<

efisiensi, tetapi dengan lebih banyak upaya saya bisa membuat StringBuilder yang lebih cepat dalam beberapa kasus.

Setiap kali saya menambahkan string, saya hanya menyimpan referensi di suatu tempat dan menambah penghitung ukuran total.

Cara nyata saya akhirnya mengimplementasikannya (Horor!) Adalah dengan menggunakan buffer buram (std :: vector <char>):

  • Header 1 byte (2 bit untuk mengetahui apakah data berikut adalah: string yang dipindahkan, string atau byte [])
  • 6 bit untuk mengetahui panjang byte []

untuk byte []

  • Saya menyimpan langsung byte string pendek (untuk akses memori berurutan)

untuk string yang dipindahkan (string ditambahkan dengan std::move)

  • Penunjuk ke std::stringobjek (kami memiliki kepemilikan)
  • atur flag di kelas jika ada byte cadangan yang tidak digunakan di sana

untuk string

  • Penunjuk ke std::stringobjek (tidak ada kepemilikan)

Ada juga satu optimasi kecil, jika string yang dimasukkan terakhir dipindahkan, itu memeriksa byte gratis tetapi tidak terpakai dan menyimpan byte lebih lanjut di sana daripada menggunakan buffer buram (ini adalah untuk menghemat beberapa memori, itu sebenarnya membuatnya sedikit lebih lambat , mungkin juga bergantung pada CPU, dan jarang melihat string dengan ruang ekstra tetap)

Ini akhirnya sedikit lebih cepat daripada std::ostringstreamtetapi memiliki beberapa kelemahan:

  • Saya berasumsi tipe lenght tetap (jadi 1,2 atau 4 byte, tidak baik untuk UTF8), saya tidak mengatakan itu tidak akan berfungsi untuk UTF8, Hanya saja saya tidak memeriksanya untuk kemalasan.
  • Saya menggunakan praktik pengkodean yang buruk (buffer buram, mudah membuatnya tidak portabel, saya percaya tambang itu portabel)
  • Tidak memiliki semua fitur ostringstream
  • Jika beberapa string yang dirujuk dihapus sebelum menggabungkan semua string: perilaku tidak terdefinisi.

kesimpulan? menggunakan std::ostringstream

Ini sudah memperbaiki bottleneck terbesar sementara kecepatan beberapa% poin dengan implementasi tambang tidak sebanding dengan kerugiannya.

CoffeDeveloper
sumber