C ++ เทียบเท่ากับ StringBuffer / StringBuilder?


184

มีคลาสไลบรารีแม่แบบ C ++ ที่มีฟังก์ชันการต่อสตริงที่มีประสิทธิภาพคล้ายกับStringBuilderของ C # หรือStringBufferของ Java หรือไม่


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มีขนาดใหญ่มันจะเติบโตเป็นครั้งคราวด้วยวิธีใดวิธีหนึ่ง ด้านบนของมันเพิ่มการดำเนินการคัดลอกซ้ำซ้อนและอาจreallocations เพิ่มเติม (จึงเรียกไปที่new/ delete) เนื่องจากคุณจะผนวกกับสตริงสั้น ๆ
BeeOnRope

@BeeOnRope ฉันเห็นด้วยกับคุณ
Iain

ฉันค่อนข้างมั่นใจว่าstr.reserve(1024);จะเร็วกว่าสิ่งนี้
hanshenrik

160

วิธี C ++ จะใช้std :: stringstreamหรือแค่การต่อสายธรรมดา สตริง C ++ นั้นไม่แน่นอนดังนั้นการพิจารณาประสิทธิภาพของการต่อข้อมูลจะน้อยกว่าข้อกังวล

เกี่ยวกับการจัดรูปแบบที่คุณสามารถทำได้ทุกรูปแบบเดียวกันในกระแส แต่ในทางที่แตกต่างกันเพื่อที่คล้ายกัน coutหรือคุณสามารถใช้ functor พิมพ์อย่างยิ่งซึ่ง encapsulate นี้และให้ String.Format เช่น interface เช่นboost :: format


59
สตริง C ++ นั้นไม่แน่นอน : เหตุผลทั้งหมดStringBuilderที่มีอยู่คือการครอบคลุมการขาดประสิทธิภาพของการเปลี่ยนรูปชนิดสตริง Java ขั้นพื้นฐานของ กล่าวอีกนัยหนึ่งStringBuilderคือการเย็บปะติดปะต่อกันดังนั้นเราควรดีใจที่เราไม่ต้องการคลาสดังกล่าวใน C ++
bobobobo

57
@bobobobo สตริงไม่เปลี่ยนรูปมีผลประโยชน์อื่น ๆ แม้ว่าม้าสำหรับหลักสูตร
jk

8
การต่อสตริงธรรมดาไม่ได้สร้างวัตถุใหม่ดังนั้นปัญหาเช่นเดียวกับการเปลี่ยนไม่ได้ใน Java? พิจารณาตัวแปรทั้งหมดเป็นสตริงในตัวอย่างต่อไปนี้: a = b + c + d + e + f; มันจะไม่โทรหาโอเปอเรเตอร์ + บน b และ c จากนั้นโอเปอเรเตอร์ + ที่ผลลัพธ์และ d เป็นต้น
Serge Rogatch

9
รอสักครู่หนึ่งคนคลาสสตริงมาตรฐานรู้วิธีการเปลี่ยนแปลงตัวเอง แต่นั่นไม่ได้หมายความว่าไม่มีประสิทธิภาพ เท่าที่ฉันรู้ std :: string ไม่สามารถขยายขนาดของ char ภายใน * ได้ นั่นหมายถึงการกลายพันธุ์ในลักษณะที่ต้องใช้อักขระเพิ่มต้องมีการจัดสรรใหม่และคัดลอก มันไม่ต่างไปจากเวกเตอร์ของตัวอักษรและมันก็ดีกว่าที่จะจองพื้นที่ที่คุณต้องการในกรณีนั้น
Trygve Skogsholm

7
@TrygveSkogsholm - ไม่แตกต่างจากเวกเตอร์ของตัวอักษร แต่แน่นอนว่า "ความจุ" ของสตริงอาจมีขนาดใหญ่กว่าขนาดของมันดังนั้นจึงไม่จำเป็นต้องทำการจัดสรรใหม่ โดยทั่วไปแล้วสตริงจะใช้กลยุทธ์การเติบโตแบบเอ็กซ์โปเนนเชียลดังนั้นการผนวกยังคงตัดจำหน่ายเป็นการดำเนินการต้นทุนเชิงเส้น ซึ่งแตกต่างจาก Strings ที่ไม่เปลี่ยนรูปแบบของ Java ซึ่งการดำเนินการต่อท้ายทุกอันจำเป็นต้องคัดลอกอักขระทั้งหมดใน Strings ทั้งสองไปยังอันใหม่ดังนั้นชุดของการต่อท้ายจึงกลายเป็นแบบ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 # StringBuilderฉันเชื่อว่าsnprintf(หรือsprintfถ้าคุณต้องการเสี่ยงที่จะเขียนโค้ด buggy ;-)) ลงในอาเรย์ตัวละครแล้วแปลงกลับเป็นสตริงเป็นตัวเลือกเท่านั้น


ไม่ใช่ในลักษณะเดียวกับ printf หรือ String ของ. NET รูปแบบแม้ว่าพวกเขาจะ?
Andy Shellam

1
มันมีเลศนัยเล็กน้อยที่จะบอกว่าพวกเขาเป็นเพียงวิธีเดียวเท่านั้น
jk

2
@jk - เป็นวิธีเดียวที่เมื่อเปรียบเทียบความสามารถในการจัดรูปแบบของ. ของ StringBuilder ซึ่งเป็นคำถามเดิมที่ถามโดยเฉพาะ ฉันพูดว่า "ฉันเชื่อ" ดังนั้นฉันอาจผิด แต่คุณสามารถแสดงวิธีรับฟังก์ชันการทำงานของ StringBuilder ใน C ++ โดยไม่ต้องใช้ printf ได้ไหม
Andy Shellam

อัปเดตคำตอบของฉันเพื่อรวมตัวเลือกการจัดรูปแบบอื่น
jk

6

เนื่องจากstd::stringใน C ++ นั้นไม่แน่นอนคุณจึงสามารถใช้มันได้ มันมี+= operatorและappendฟังก์ชั่น

หากคุณต้องการผนวกข้อมูลตัวเลขให้ใช้std::to_stringฟังก์ชัน

ถ้าคุณต้องการความยืดหยุ่นมากขึ้นในรูปแบบของความสามารถในการทำให้เป็นอันดับวัตถุใด ๆ กับสายอักขระแล้วใช้std::stringstreamคลาส แต่คุณจะต้องใช้ฟังก์ชั่นผู้ให้บริการสตรีมมิ่งของคุณเองเพื่อให้สามารถทำงานกับคลาสที่คุณกำหนดเองได้


4

std :: string's + = ไม่ทำงานกับ const char * (สิ่งที่ชอบ "string to add" ดูเหมือนจะเป็น) ดังนั้นการใช้ stringstream จะใกล้เคียงกับสิ่งที่ต้องการมากที่สุด - คุณเพียงแค่ใช้ << แทนที่จะเป็น +


3

ตัวสร้างสตริงที่สะดวกสำหรับ c ++

เช่นเดียวกับหลายคนตอบก่อนหน้านี้ std :: stringstream เป็นวิธีการที่เลือก มันใช้งานได้ดีและมีตัวเลือกการแปลงและการจัดรูปแบบมากมาย IMO มันมีข้อบกพร่องที่ไม่สะดวกอย่างหนึ่งแม้ว่า: คุณไม่สามารถใช้มันเป็นหนึ่งซับหรือเป็นนิพจน์ คุณต้องเขียนเสมอ:

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

ซึ่งค่อนข้างน่ารำคาญโดยเฉพาะอย่างยิ่งเมื่อคุณต้องการเริ่มต้นสตริงในตัวสร้าง

เหตุผลก็คือก) std :: stringstream ไม่มีโอเปอเรเตอร์การแปลงเป็น std :: string และ b) โอเปอเรเตอร์ << () ของสตริงสตรีมไม่ส่งคืนการอ้างอิงสตริงสตรีม แต่เป็นการอ้างอิง stream :: 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

เชือกภาชนะอาจจะคุ้มค่าหากต้องใส่สตริง / ลบเข้าไปในสถานที่ที่สุ่มของสตริงปลายทางหรือสำหรับลำดับถ่านยาว นี่คือตัวอย่างจากการใช้งานของ 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<<

ประสิทธิภาพ แต่ด้วย attemps ที่มากขึ้นฉันสามารถสร้าง StringBuilder ที่เร็วขึ้นในบางกรณี

ทุกครั้งที่ฉันต่อท้ายสตริงฉันจะเก็บการอ้างอิงไว้ที่ใดที่หนึ่งและเพิ่มตัวนับขนาดรวม

ในที่สุดวิธีที่ฉันใช้จริง (Horror!) คือการใช้ opaque buffer (std :: vector <char>):

  • ส่วนหัว 1 ไบต์ (2 บิตเพื่อบอกว่าข้อมูลต่อไปนี้คือสตริงที่ย้ายสตริงหรือไบต์ [])
  • 6 บิตเพื่อบอกความยาวของไบต์ []

สำหรับไบต์ []

  • ฉันเก็บไบต์สั้น ๆ ของสตริงสั้น ๆ (สำหรับการเข้าถึงหน่วยความจำตามลำดับ)

สำหรับสตริงที่ย้าย (สตริงที่ต่อท้ายด้วยstd::move)

  • ตัวชี้ไปยังstd::stringวัตถุ (เรามีกรรมสิทธิ์)
  • ตั้งค่าสถานะในคลาสถ้ามีไบต์ที่สงวนไว้ที่ไม่ได้ใช้ที่นั่น

สำหรับสตริง

  • ตัวชี้ไปยังstd::stringวัตถุ (ไม่มีความเป็นเจ้าของ)

นอกจากนี้ยังมีการเพิ่มประสิทธิภาพเล็ก ๆ น้อย ๆ หนึ่งถ้าสตริงที่แทรกล่าสุดเป็น mov'd ในมันจะตรวจสอบไบต์ที่สงวนไว้ฟรี แต่ไม่ได้ใช้และจัดเก็บไบต์เพิ่มเติมในที่นั่นแทนที่จะใช้บัฟเฟอร์ทึบแสง (นี่คือการบันทึกหน่วยความจำบางส่วน อาจขึ้นอยู่กับ CPU ด้วยและหายากที่จะเห็นสตริงที่มีพื้นที่สงวนพิเศษอยู่แล้ว)

ในที่สุดนี่ก็เร็วกว่าเล็กน้อยstd::ostringstreamแต่มีข้อเสียเล็กน้อย:

  • ฉันสันนิษฐานว่าประเภทความยาวคงที่ (เช่น 1,2 หรือ 4 ไบต์ไม่ดีสำหรับ UTF8) ฉันไม่ได้บอกว่ามันจะไม่ทำงานสำหรับ UTF8 เพียงแค่ฉันไม่ตรวจสอบความเกียจคร้าน
  • ฉันใช้การเข้ารหัสที่ไม่ดี (บัฟเฟอร์ทึบแสงง่ายต่อการทำให้ไม่พกพาฉันเชื่อว่าวิธีพกพาของฉันเป็นวิธีการ)
  • ขาดคุณสมบัติทั้งหมดของ ostringstream
  • หากบางสายอ้างอิงถูกลบก่อนที่จะรวมสตริงทั้งหมด: พฤติกรรมที่ไม่ได้กำหนด

สรุป? ใช้ std::ostringstream

มันแก้ไขปัญหาคอขวดที่ใหญ่ที่สุดในขณะที่ทำคะแนนเพียงไม่กี่% ของความเร็วด้วยการติดตั้งของฉันไม่คุ้มกับข้อเสีย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.