มีคลาสไลบรารีแม่แบบ C ++ ที่มีฟังก์ชันการต่อสตริงที่มีประสิทธิภาพคล้ายกับStringBuilderของ C # หรือStringBufferของ Java หรือไม่
มีคลาสไลบรารีแม่แบบ C ++ ที่มีฟังก์ชันการต่อสตริงที่มีประสิทธิภาพคล้ายกับStringBuilderของ C # หรือStringBufferของ Java หรือไม่
คำตอบ:
หมายเหตุคำตอบนี้ได้รับความสนใจเมื่อเร็ว ๆ นี้ ฉันไม่ได้เรียกร้องให้สิ่งนี้เป็นวิธีแก้ปัญหา (เป็นวิธีแก้ปัญหาที่ฉันเคยเห็นก่อนหน้า 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 มันนานมาแล้ว หากคุณนำกลยุทธ์เช่นนี้มาใช้โปรไฟล์ของคุณก่อน
scratch
สตริงทำอะไรที่นี่ได้จริงๆ จำนวนการจัดสรรใหม่ของสตริงหลักนั้นส่วนใหญ่จะเป็นฟังก์ชั่นของขนาดสุดท้ายไม่ใช่จำนวนของการดำเนินการต่อท้ายเว้นแต่การstring
ดำเนินการนั้นแย่มาก (กล่าวคือไม่ได้ใช้การเติบโตแบบเลขชี้กำลัง) ดังนั้น "แบทช์" อัพappend
ไม่ช่วยเพราะเมื่อต้นแบบstring
มีขนาดใหญ่มันจะเติบโตเป็นครั้งคราวด้วยวิธีใดวิธีหนึ่ง ด้านบนของมันเพิ่มการดำเนินการคัดลอกซ้ำซ้อนและอาจreallocations เพิ่มเติม (จึงเรียกไปที่new
/ delete
) เนื่องจากคุณจะผนวกกับสตริงสั้น ๆ
str.reserve(1024);
จะเร็วกว่าสิ่งนี้
วิธี C ++ จะใช้std :: stringstreamหรือแค่การต่อสายธรรมดา สตริง C ++ นั้นไม่แน่นอนดังนั้นการพิจารณาประสิทธิภาพของการต่อข้อมูลจะน้อยกว่าข้อกังวล
เกี่ยวกับการจัดรูปแบบที่คุณสามารถทำได้ทุกรูปแบบเดียวกันในกระแส แต่ในทางที่แตกต่างกันเพื่อที่คล้ายกัน cout
หรือคุณสามารถใช้ functor พิมพ์อย่างยิ่งซึ่ง encapsulate นี้และให้ String.Format เช่น interface เช่นboost :: format
StringBuilder
ที่มีอยู่คือการครอบคลุมการขาดประสิทธิภาพของการเปลี่ยนรูปชนิดสตริง Java ขั้นพื้นฐานของ กล่าวอีกนัยหนึ่งStringBuilder
คือการเย็บปะติดปะต่อกันดังนั้นเราควรดีใจที่เราไม่ต้องการคลาสดังกล่าวใน C ++
O(n)
ทั่วไป
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();
std::string
เป็น c ++ เทียบเท่า: มันไม่แน่นอน
คุณสามารถใช้. append () สำหรับการต่อสตริง
std::string s = "string1";
s.append("string2");
ฉันคิดว่าคุณอาจทำได้:
std::string s = "string1";
s += "string2";
สำหรับการดำเนินการจัดรูปแบบของ C # StringBuilder
ฉันเชื่อว่าsnprintf
(หรือsprintf
ถ้าคุณต้องการเสี่ยงที่จะเขียนโค้ด buggy ;-)) ลงในอาเรย์ตัวละครแล้วแปลงกลับเป็นสตริงเป็นตัวเลือกเท่านั้น
เนื่องจากstd::string
ใน C ++ นั้นไม่แน่นอนคุณจึงสามารถใช้มันได้ มันมี+= operator
และappend
ฟังก์ชั่น
หากคุณต้องการผนวกข้อมูลตัวเลขให้ใช้std::to_string
ฟังก์ชัน
ถ้าคุณต้องการความยืดหยุ่นมากขึ้นในรูปแบบของความสามารถในการทำให้เป็นอันดับวัตถุใด ๆ กับสายอักขระแล้วใช้std::stringstream
คลาส แต่คุณจะต้องใช้ฟังก์ชั่นผู้ให้บริการสตรีมมิ่งของคุณเองเพื่อให้สามารถทำงานกับคลาสที่คุณกำหนดเองได้
std :: string's + = ไม่ทำงานกับ const char * (สิ่งที่ชอบ "string to add" ดูเหมือนจะเป็น) ดังนั้นการใช้ stringstream จะใกล้เคียงกับสิ่งที่ต้องการมากที่สุด - คุณเพียงแค่ใช้ << แทนที่จะเป็น +
ตัวสร้างสตริงที่สะดวกสำหรับ 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
ไม่ประพฤติเช่นนี้
เชือกภาชนะอาจจะคุ้มค่าหากต้องใส่สตริง / ลบเข้าไปในสถานที่ที่สุ่มของสตริงปลายทางหรือสำหรับลำดับถ่านยาว นี่คือตัวอย่างจากการใช้งานของ 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.
ฉันต้องการเพิ่มสิ่งใหม่เนื่องจากสิ่งต่อไปนี้:
ในตอนแรกฉันไม่สามารถเอาชนะได้
std::ostringstream
's operator<<
ประสิทธิภาพ แต่ด้วย attemps ที่มากขึ้นฉันสามารถสร้าง StringBuilder ที่เร็วขึ้นในบางกรณี
ทุกครั้งที่ฉันต่อท้ายสตริงฉันจะเก็บการอ้างอิงไว้ที่ใดที่หนึ่งและเพิ่มตัวนับขนาดรวม
ในที่สุดวิธีที่ฉันใช้จริง (Horror!) คือการใช้ opaque buffer (std :: vector <char>):
สำหรับไบต์ []
สำหรับสตริงที่ย้าย (สตริงที่ต่อท้ายด้วยstd::move
)
std::string
วัตถุ (เรามีกรรมสิทธิ์)สำหรับสตริง
std::string
วัตถุ (ไม่มีความเป็นเจ้าของ)นอกจากนี้ยังมีการเพิ่มประสิทธิภาพเล็ก ๆ น้อย ๆ หนึ่งถ้าสตริงที่แทรกล่าสุดเป็น mov'd ในมันจะตรวจสอบไบต์ที่สงวนไว้ฟรี แต่ไม่ได้ใช้และจัดเก็บไบต์เพิ่มเติมในที่นั่นแทนที่จะใช้บัฟเฟอร์ทึบแสง (นี่คือการบันทึกหน่วยความจำบางส่วน อาจขึ้นอยู่กับ CPU ด้วยและหายากที่จะเห็นสตริงที่มีพื้นที่สงวนพิเศษอยู่แล้ว)
ในที่สุดนี่ก็เร็วกว่าเล็กน้อยstd::ostringstream
แต่มีข้อเสียเล็กน้อย:
ostringstream
สรุป? ใช้
std::ostringstream
มันแก้ไขปัญหาคอขวดที่ใหญ่ที่สุดในขณะที่ทำคะแนนเพียงไม่กี่% ของความเร็วด้วยการติดตั้งของฉันไม่คุ้มกับข้อเสีย
std::ostringstream
มีระดับที่และเป็น