การต่อสายอักขระที่มีประสิทธิภาพใน C ++


108

ฉันได้ยินว่ามีคนสองสามคนแสดงความกังวลเกี่ยวกับตัวดำเนินการ "+" ใน std :: string และวิธีแก้ปัญหาต่างๆเพื่อเร่งการเชื่อมต่อ สิ่งเหล่านี้จำเป็นจริงๆหรือไม่? ถ้าเป็นเช่นนั้นวิธีที่ดีที่สุดในการเชื่อมสตริงใน C ++ คืออะไร?


13
โดยทั่วไป + ไม่ใช่ตัวดำเนินการเรียงต่อกัน (เนื่องจากสร้างสตริงใหม่) ใช้ + = สำหรับการเรียงต่อกัน
Martin York

1
ตั้งแต่ C ++ 11 มีจุดสำคัญ: ตัวดำเนินการ + สามารถแก้ไขตัวถูกดำเนินการตัวใดตัวหนึ่งและส่งคืนโดยย้ายหากตัวถูกดำเนินการนั้นถูกส่งผ่านโดยการอ้างอิงค่า rvalue libstdc++ ทำเช่นนี้ ดังนั้นเมื่อเรียกใช้ operator + ด้วย temporaries มันสามารถบรรลุประสิทธิภาพเกือบดี - อาจเป็นข้อโต้แย้งที่สนับสนุนการผิดนัดเพื่อให้อ่านง่ายเว้นแต่จะมีเกณฑ์มาตรฐานที่แสดงว่าเป็นคอขวด อย่างไรก็ตามรูปแบบมาตรฐานappend()จะเหมาะสมที่สุดและสามารถอ่านได้ ...
underscore_d

คำตอบ:


86

การทำงานพิเศษอาจไม่คุ้มค่าเว้นแต่คุณจะต้องการประสิทธิภาพจริงๆ คุณอาจจะมีประสิทธิภาพที่ดีขึ้นมากเพียงแค่ใช้ตัวดำเนินการ + = แทน

หลังจากข้อจำกัดความรับผิดชอบนั้นฉันจะตอบคำถามที่แท้จริงของคุณ ...

ประสิทธิภาพของคลาสสตริง STL ขึ้นอยู่กับการนำ STL ที่คุณใช้อยู่

คุณสามารถรับประกันประสิทธิภาพและควบคุมตัวเองได้มากขึ้นด้วยการเชื่อมต่อด้วยตนเองผ่านฟังก์ชั่นในตัว c

เหตุใดตัวดำเนินการ + จึงไม่มีประสิทธิภาพ:

ดูอินเทอร์เฟซนี้:

template <class charT, class traits, class Alloc>
basic_string<charT, traits, Alloc>
operator+(const basic_string<charT, traits, Alloc>& s1,
          const basic_string<charT, traits, Alloc>& s2)

คุณจะเห็นว่ามีการส่งคืนวัตถุใหม่หลังจากแต่ละ + นั่นหมายความว่าจะมีการใช้บัฟเฟอร์ใหม่ในแต่ละครั้ง หากคุณกำลังดำเนินการพิเศษ + จำนวนมากมันจะไม่มีประสิทธิภาพ

ทำไมคุณสามารถทำให้มีประสิทธิภาพมากขึ้น:

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

ข้อควรพิจารณาในการใช้งาน:

  • ติดตามความยาวสตริง
  • ให้ตัวชี้ไปที่จุดสิ้นสุดของสตริงและจุดเริ่มต้นหรือเพียงแค่จุดเริ่มต้นและใช้จุดเริ่มต้น + ความยาวเป็นออฟเซ็ตเพื่อค้นหาจุดสิ้นสุดของสตริง
  • ตรวจสอบให้แน่ใจว่าบัฟเฟอร์ที่คุณจัดเก็บสตริงของคุณมีขนาดใหญ่พอคุณจึงไม่จำเป็นต้องจัดสรรข้อมูลใหม่
  • ใช้ strcpy แทน strcat ดังนั้นคุณจึงไม่จำเป็นต้องวนซ้ำตามความยาวของสตริงเพื่อค้นหาจุดสิ้นสุดของสตริง

โครงสร้างข้อมูลเชือก:

หากคุณต้องการได้อย่างรวดเร็วจริงๆ concatenations พิจารณาใช้โครงสร้างข้อมูลเชือก


6
หมายเหตุ: "STL" หมายถึงไลบรารีโอเพนซอร์สที่แยกจากกันโดยสิ้นเชิง HP แต่เดิมบางส่วนใช้เป็นพื้นฐานสำหรับบางส่วนของไลบรารี ISO Standard C ++ อย่างไรก็ตาม "std :: string" ไม่เคยเป็นส่วนหนึ่งของ STL ของ HP ดังนั้นจึงผิดอย่างสิ้นเชิงที่จะอ้างอิง "STL และ" string "ร่วมกัน
James Curran

1
ฉันจะไม่บอกว่ามันผิดที่จะใช้ STL และสตริงร่วมกัน ดูsgi.com/tech/stl/table_of_contents.html
Brian

1
เมื่อ SGI เข้ารับการบำรุงรักษา STL จาก HP มันได้รับการติดตั้งแบบย้อนยุคเพื่อให้เข้ากับ Standard Library (นั่นคือเหตุผลที่ฉันบอกว่า "ไม่เคยเป็นส่วนหนึ่งของ STL ของ HP") อย่างไรก็ตามผู้ริเริ่มของ std :: string คือคณะกรรมการ ISO C ++
James Curran

2
หมายเหตุด้านข้าง: พนักงาน SGI ที่รับผิดชอบดูแล STL เป็นเวลาหลายปีคือ Matt Austern ซึ่งเป็นหัวหน้ากลุ่มย่อย Library ของคณะกรรมการมาตรฐาน ISO C ++ ในเวลาเดียวกัน
James Curran

4
คุณช่วยชี้แจงหรือให้ประเด็นได้ไหมว่าทำไมคุณถึงใช้สแต็กสำหรับบัฟเฟอร์แทนฮีปซึ่งมีประสิทธิภาพมากกว่ามาก เหรอ? ความแตกต่างของประสิทธิภาพนี้มาจากไหน?
h7r

76

จองพื้นที่สุดท้ายของคุณก่อนจากนั้นใช้วิธีต่อท้ายด้วยบัฟเฟอร์ ตัวอย่างเช่นสมมติว่าคุณคาดหวังให้ความยาวสตริงสุดท้ายคือ 1 ล้านอักขระ:

std::string s;
s.reserve(1000000);

while (whatever)
{
  s.append(buf,len);
}

17

ฉันจะไม่กังวลเกี่ยวกับมัน หากคุณทำแบบวนซ้ำสตริงจะจัดสรรหน่วยความจำล่วงหน้าเสมอเพื่อลดการจัดสรรใหม่ - เพียงใช้operator+=ในกรณีนั้น และถ้าคุณทำด้วยตนเองสิ่งนี้หรือนานกว่านั้น

a + " : " + c

จากนั้นเป็นการสร้าง Temporaries แม้ว่าคอมไพเลอร์จะสามารถกำจัดสำเนาค่าส่งคืนบางส่วนได้ นั่นเป็นเพราะในการเรียกแบบต่อเนื่องoperator+ไม่ทราบว่าพารามิเตอร์อ้างอิงอ้างอิงอ็อบเจ็กต์ที่มีชื่อหรือส่งคืนชั่วคราวจากการoperator+เรียกใช้ย่อย ฉันไม่ควรกังวลเกี่ยวกับเรื่องนี้ก่อนที่จะไม่ได้ทำโปรไฟล์ก่อน แต่ลองมาเป็นตัวอย่างเพื่อแสดงให้เห็นว่า อันดับแรกเราแนะนำวงเล็บเพื่อให้การเชื่อมโยงชัดเจน ฉันใส่อาร์กิวเมนต์โดยตรงหลังจากการประกาศฟังก์ชันที่ใช้เพื่อความชัดเจน ด้านล่างนี้ฉันจะแสดงว่านิพจน์ผลลัพธ์คืออะไร:

((a + " : ") + c) 
calls string operator+(string const&, char const*)(a, " : ")
  => (tmp1 + c)

ตอนนี้นอกเหนือจากนั้นtmp1คือสิ่งที่ส่งกลับมาจากการเรียกตัวดำเนินการครั้งแรก + พร้อมด้วยอาร์กิวเมนต์ที่แสดง เราถือว่าคอมไพเลอร์ฉลาดมากและปรับสำเนาค่าส่งคืนให้เหมาะสม ดังนั้นเราจึงจบลงด้วยสตริงใหม่หนึ่งที่มีการเรียงต่อกันของและa " : "ตอนนี้สิ่งนี้เกิดขึ้น:

(tmp1 + c)
calls string operator+(string const&, string const&)(tmp1, c)
  => tmp2 == <end result>

เปรียบเทียบกับสิ่งต่อไปนี้:

std::string f = "hello";
(f + c)
calls string operator+(string const&, string const&)(f, c)
  => tmp1 == <end result>

มันใช้ฟังก์ชันเดียวกันสำหรับสตริงชั่วคราวและสตริงที่มีชื่อ! ดังนั้นคอมไพเลอร์จึงต้องคัดลอกอาร์กิวเมนต์ไปยังสตริงใหม่และต่อท้ายและส่งคืนจากเนื้อหาของoperator+. ไม่สามารถใช้หน่วยความจำชั่วคราวและผนวกเข้ากับสิ่งนั้นได้ ยิ่งนิพจน์ใหญ่เท่าไหร่ก็ยิ่งต้องทำสำเนาสตริงมากขึ้นเท่านั้น

Visual Studio และ GCC ถัดไปจะรองรับความหมายการเคลื่อนที่ของ c ++ 1x (การเสริมความหมายของการคัดลอก ) และการอ้างอิงค่า rvalue เป็นการเพิ่มการทดลอง ซึ่งช่วยให้ทราบว่าพารามิเตอร์อ้างถึงชั่วคราวหรือไม่ สิ่งนี้จะทำให้การเพิ่มดังกล่าวรวดเร็วอย่างน่าอัศจรรย์เนื่องจากสิ่งที่กล่าวมาทั้งหมดจะจบลงใน "add-pipeline" อันเดียวโดยไม่มีสำเนา

หากกลายเป็นคอขวดคุณยังสามารถทำได้

 std::string(a).append(" : ").append(c) ...

การappendโทรต่อท้ายอาร์กิวเมนต์*thisและส่งคืนการอ้างอิงถึงตัวเอง ดังนั้นจึงไม่มีการคัดลอกชั่วคราว หรืออีกวิธีหนึ่งคือoperator+=สามารถใช้ได้ แต่คุณจะต้องมีวงเล็บที่น่าเกลียดเพื่อแก้ไขลำดับความสำคัญ


ฉันต้องตรวจสอบตัวดำเนินการ stdlib ทำสิ่งนี้จริงๆ : P libstdc++สำหรับoperator+(string const& lhs, string&& rhs)does return std::move(rhs.insert(0, lhs)). แล้วถ้าทั้งสองชั่วคราวของมันoperator+(string&& lhs, string&& rhs)ถ้ามีความจุที่เพียงพอประสงค์ใช้ได้เพียงโดยตรงlhs append()ในกรณีที่ฉันคิดว่าความเสี่ยงนี้จะช้ากว่าที่operator+=เป็นอยู่หากlhsไม่มีความจุเพียงพอเนื่องจากจะกลับไปrhs.insert(0, lhs)ซึ่งไม่เพียง แต่ต้องขยายบัฟเฟอร์และเพิ่มเนื้อหาใหม่เช่นappend()แต่ยังต้องเปลี่ยนไปตามเนื้อหาดั้งเดิมของrhsด้านขวา
underscore_d

ค่าใช้จ่ายส่วนอื่นเมื่อเทียบกับoperator+=คือoperator+ยังคงต้องส่งคืนค่าดังนั้นจึงต้องมีค่าใช้จ่ายmove()ใด ๆ ที่ต่อท้าย ถึงกระนั้นฉันคิดว่านั่นเป็นค่าใช้จ่ายที่ค่อนข้างน้อย (คัดลอกพอยน์เตอร์ / ขนาดสองสามตัว) เมื่อเทียบกับการคัดลอกสตริงทั้งหมดในรายละเอียดดังนั้นจึงเป็นการดี!
underscore_d

11

สำหรับแอปพลิเคชันส่วนใหญ่มันจะไม่สำคัญ เพียงแค่เขียนโค้ดของคุณโดยไม่รู้ตัวว่าตัวดำเนินการ + ทำงานอย่างไรอย่างมีความสุขและลงมือทำเองก็ต่อเมื่อมันกลายเป็นคอขวดที่ชัดเจน


7
แน่นอนว่ามันไม่คุ้มค่าสำหรับกรณีส่วนใหญ่ แต่สิ่งนี้ไม่ได้ตอบคำถามของเขาจริงๆ
Brian R.Bondy

1
ใช่. ฉันยอมรับเพียงแค่พูดว่า "profile then optimize" สามารถใส่เป็นความคิดเห็นในคำถาม :)
Johannes Schaub - litb

6
ในทางเทคนิคเขาถามว่า "จำเป็น" หรือไม่ ไม่ใช่และนี่ตอบคำถามนั้น
Samantha Branham

พอใช้ แต่จำเป็นสำหรับบางแอปพลิเคชัน ดังนั้นในแอปพลิเคชันเหล่านั้นคำตอบจึงลดลงเป็น: 'จัดการเรื่องต่างๆด้วยมือของคุณเอง'
Brian R.Bondy

4
@Pesto มีแนวคิดในทางที่ผิดในโลกของการเขียนโปรแกรมว่าประสิทธิภาพไม่สำคัญและเราสามารถเพิกเฉยต่อข้อตกลงทั้งหมดได้เนื่องจากคอมพิวเตอร์ทำงานเร็วขึ้นเรื่อย ๆ นั่นไม่ใช่เหตุผลที่ผู้คนเขียนโปรแกรมใน C ++ และนั่นไม่ใช่เหตุผลที่พวกเขาโพสต์คำถามเกี่ยวกับ stack overflow เกี่ยวกับการต่อสตริงที่มีประสิทธิภาพ
MrFox

7

ซึ่งแตกต่างจาก. NET System.Strings, C ++ ของ std :: strings สามารถเปลี่ยนแปลงได้ดังนั้นจึงสามารถสร้างได้ด้วยการเชื่อมต่อแบบง่าย ๆ เช่นเดียวกับวิธีการอื่น ๆ


2
โดยเฉพาะอย่างยิ่งถ้าคุณใช้ reserve () เพื่อทำให้บัฟเฟอร์ใหญ่พอสำหรับผลลัพธ์ก่อนที่จะเริ่ม
Mark Ransom

ฉันคิดว่าเขากำลังพูดถึงตัวดำเนินการ + = นอกจากนี้ยังเชื่อมต่อกันแม้ว่าจะเป็นกรณีที่เสื่อมโทรม james เป็น vc ++ mvp ดังนั้นฉันคาดว่าเขามีเงื่อนงำบางอย่างของ c ++: p
Johannes Schaub - litb

1
ฉันไม่สงสัยเลยสักวินาทีที่เขามีความรู้มากมายเกี่ยวกับ C ++ เพียงแค่ว่ามีความเข้าใจผิดเกี่ยวกับคำถามนั้น คำถามถามเกี่ยวกับประสิทธิภาพของตัวดำเนินการ + ซึ่งส่งคืนอ็อบเจ็กต์สตริงใหม่ทุกครั้งที่มีการเรียกใช้ดังนั้นจึงใช้ถ่านบัฟเฟอร์ใหม่
Brian R.Bondy

1
ใช่. แต่แล้วเขาก็ถามหาตัวดำเนินการเคส + ช้าวิธีที่ดีที่สุดคือทำการต่อกัน และที่นี่ตัวดำเนินการ + = เข้ามาในเกม แต่ฉันยอมรับว่าคำตอบของเจมส์สั้นไปหน่อย ทำให้ดูเหมือนว่าเราทุกคนสามารถใช้ตัวดำเนินการ + และมีประสิทธิภาพสูงสุด: p
Johannes Schaub - litb

@ BrianR.Bondy operator+ไม่ต้องส่งคืนสตริงใหม่ ตัวดำเนินการสามารถส่งคืนตัวถูกดำเนินการตัวใดตัวหนึ่งแก้ไขได้หากตัวถูกดำเนินการนั้นถูกส่งผ่านโดยการอ้างอิงค่า rvalue libstdc++ ทำเช่นนี้ ดังนั้นเมื่อเรียกใช้operator+ด้วย temporaries ก็สามารถบรรลุประสิทธิภาพที่ดีเหมือนกันหรือเกือบเท่ากันได้ซึ่งอาจเป็นข้อโต้แย้งอื่นที่สนับสนุนการผิดนัดเว้นแต่จะมีเกณฑ์มาตรฐานที่แสดงว่าเป็นคอขวด
underscore_d

5

บางที std :: stringstream แทน?

แต่ฉันเห็นด้วยกับความเชื่อมั่นที่ว่าคุณควรรักษาไว้ให้ดีและเข้าใจได้จากนั้นโปรไฟล์เพื่อดูว่าคุณกำลังมีปัญหาจริงๆหรือไม่



1
@ArtemGr stringstream อาจจะเร็วดูcodeproject.com/Articles/647856/…
mloskot

4

ในImperfect C ++ Matthew Wilson นำเสนอตัวเชื่อมต่อสตริงแบบไดนามิกที่คำนวณล่วงหน้าความยาวของสตริงสุดท้ายเพื่อให้มีการจัดสรรเพียงครั้งเดียวก่อนที่จะเชื่อมต่อส่วนทั้งหมด นอกจากนี้เรายังสามารถใช้ concatenator คงที่ด้วยการเล่นกับแม่แบบการแสดงออก

ความคิดแบบนั้นได้ถูกนำไปใช้ใน STLport std :: การใช้งานสตริงซึ่งไม่เป็นไปตามมาตรฐานเนื่องจากการแฮ็กที่แม่นยำนี้


Glib::ustring::compose()จากการโยง glibmm ไปจนถึง GLib จะทำเช่นนั้น: ประมาณและreserve()s ความยาวสุดท้ายตามสตริงรูปแบบที่ให้มาและ varargs จากนั้นappend()s แต่ละอัน (หรือการแทนที่ในรูปแบบ) ในลูป ฉันคาดว่านี่เป็นวิธีการทำงานที่ค่อนข้างธรรมดา
underscore_d

4

std::string operator+จัดสรรสตริงใหม่และคัดลอกสองสตริงตัวถูกดำเนินการทุกครั้ง ทำซ้ำหลาย ๆ ครั้งและมีราคาแพง O (n)

std::string appendและoperator+=ในทางกลับกันกระแทกความจุ 50% ทุกครั้งที่สตริงต้องเติบโต ซึ่งช่วยลดจำนวนการจัดสรรหน่วยความจำและการคัดลอกลงอย่างมาก O (log n)


ฉันไม่ค่อยแน่ใจว่าเหตุใดจึงถูกลดคะแนน มาตรฐานไม่จำเป็นต้องใช้ตัวเลข 50% แต่ IIRC ที่หรือ 100% เป็นมาตรการทั่วไปของการเติบโตในทางปฏิบัติ อย่างอื่นในคำตอบนี้ดูเหมือนจะไม่สามารถโต้แย้งได้
underscore_d

หลายเดือนต่อมาฉันคิดว่ามันไม่ถูกต้องทั้งหมดเนื่องจากมันถูกเขียนมานานหลังจากที่ C ++ 11 เดบิวต์และการโอเวอร์โหลดoperator+ที่อาร์กิวเมนต์หนึ่งหรือทั้งสองถูกส่งผ่านโดยการอ้างอิง rvalue สามารถหลีกเลี่ยงการจัดสรรสตริงใหม่โดยรวมเข้ากับบัฟเฟอร์ที่มีอยู่ของ ตัวถูกดำเนินการตัวใดตัวหนึ่ง (แม้ว่าอาจต้องจัดสรรใหม่หากมีความจุไม่เพียงพอ)
underscore_d

2

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

ฉันชอบ std :: ostringstream สำหรับการเชื่อมต่อที่ซับซ้อน


2

เช่นเดียวกับสิ่งต่างๆส่วนใหญ่การไม่ทำอะไรบางอย่างนั้นง่ายกว่าการทำ

หากคุณต้องการส่งออกสตริงขนาดใหญ่ไปยัง GUI อาจเป็นไปได้ว่าสิ่งใดก็ตามที่คุณส่งออกมาสามารถจัดการกับสตริงเป็นชิ้น ๆ ได้ดีกว่าสตริงขนาดใหญ่ (ตัวอย่างเช่นการต่อข้อความในโปรแกรมแก้ไขข้อความ - โดยปกติจะแยกบรรทัดกัน โครงสร้าง).

หากคุณต้องการส่งออกไปยังไฟล์ให้สตรีมข้อมูลแทนที่จะสร้างสตริงขนาดใหญ่และส่งออกข้อมูลนั้น

ฉันไม่เคยพบว่าจำเป็นต้องทำการเชื่อมต่อให้เร็วขึ้นหากฉันลบการเชื่อมต่อที่ไม่จำเป็นออกจากโค้ดที่ช้า


2

น่าจะเป็นประสิทธิภาพที่ดีที่สุดหากคุณจัดสรรพื้นที่ (สำรอง) ไว้ล่วงหน้าในสตริงผลลัพธ์

template<typename... Args>
std::string concat(Args const&... args)
{
    size_t len = 0;
    for (auto s : {args...})  len += strlen(s);

    std::string result;
    result.reserve(len);    // <--- preallocate result
    for (auto s : {args...})  result += s;
    return result;
}

การใช้งาน:

std::string merged = concat("This ", "is ", "a ", "test!");

0

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

เคล็ดลับคือการจัดสรรขนาดใหญ่เพียงครั้งเดียวเมื่อเริ่มต้น

ที่

https://github.com/pedro-vicente/table-string

เกณฑ์มาตรฐาน

สำหรับ Visual Studio 2015 สร้างดีบัก x86 การปรับปรุงทางการเงินบน C ++ std :: string

| API                   | Seconds           
| ----------------------|----| 
| SDS                   | 19 |  
| std::string           | 11 |  
| std::string (reserve) | 9  |  
| table_str_t           | 1  |  

1
สหกรณ์มีความสนใจในวิธีการ std::stringconcatenate พวกเขาไม่ได้ขอคลาสสตริงทางเลือก
underscore_d

0

คุณสามารถลองใช้ตัวนี้ด้วยการจองหน่วยความจำสำหรับแต่ละรายการ:

namespace {
template<class C>
constexpr auto size(const C& c) -> decltype(c.size()) {
  return static_cast<std::size_t>(c.size());
}

constexpr std::size_t size(const char* string) {
  std::size_t size = 0;
  while (*(string + size) != '\0') {
    ++size;
  }
  return size;
}

template<class T, std::size_t N>
constexpr std::size_t size(const T (&)[N]) noexcept {
  return N;
}
}

template<typename... Args>
std::string concatStrings(Args&&... args) {
  auto s = (size(args) + ...);
  std::string result;
  result.reserve(s);
  return (result.append(std::forward<Args>(args)), ...);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.