std :: การจัดรูปแบบสตริงเช่น sprintf


454

ฉันต้องฟอร์แมตstd::stringด้วยsprintfและส่งไปที่สตรีมไฟล์ ฉันจะทำสิ่งนี้ได้อย่างไร


6
เรื่องสั้นเรื่องยาวboost::format(ตามคำตอบของ kennytm ใช้ตรงนี้ ) boost::formatรองรับผู้ให้บริการสตรีม C ++ แล้ว! ตัวอย่าง: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formatมีโค้ดน้อยที่สุด ... ตรวจสอบโดยเพื่อนและรวมเข้ากับสตรีม C ++
เทรเวอร์บอยด์สมิ ธ

@Ockonal - เพื่อประโยชน์ของชุมชน (ฉันไม่ได้สนใจน้อยลงเกี่ยวกับตัวแทนของฉัน) ฉันขอแนะนำให้คุณเปลี่ยนการเลือกของคุณ สิ่งที่เลือกไว้ในปัจจุบันในตัวอย่างแรกแสดงข้อผิดพลาดที่รอให้เกิดขึ้นในการใช้งานความยาวสูงสุดโดยพลการ ตัวอย่างที่สองไม่สนใจความต้องการที่คุณระบุไว้อย่างสมบูรณ์เพื่อใช้ vargs เช่น sprintf ฉันขอแนะนำให้คุณเลือกเฉพาะคำตอบที่นี่ที่สะอาดปลอดภัยอาศัยเฉพาะมาตรฐาน C ++ ทดสอบและแสดงความคิดเห็นอย่างดีเท่านั้น ว่ามันเป็นของฉันไม่เกี่ยวข้อง มันเป็นความจริงอย่างเป็นกลาง ดูstackoverflow.com/questions/2342162/...
Douglas Daseeco

@TrevorBoydSmith a std::formatถูกเพิ่มใน C ++ 20 BTW: stackoverflow.com/a/57286312/895245 เยี่ยมมาก !
Ciro Santilli 郝海东冠状病六四事件法轮功

1
@CiroSantilli ผมอ่านบทความเกี่ยวกับC++20เพียงแค่เมื่อวานนี้และข้าพเจ้าเห็นว่าC++20การคัดลอกboost(ในเวลาล้านในขณะนี้) โดยการเพิ่มstd::formatให้กับC++20สเป็ค! ฉันมีความสุขมากมาก! เกือบทุกภาษา C ++ ไฟล์ฉันได้เขียนในช่วง 9 boost::formatปีได้ใช้ การเพิ่มเอาต์พุตสไตล์ printf อย่างเป็นทางการให้กับสตรีมใน C ++ จะทำให้ IMO ทางไกลสำหรับ C ++ ทั้งหมด
เทรเวอร์บอยด์สมิ ธ

คำตอบ:


333

คุณไม่สามารถทำได้โดยตรงเนื่องจากคุณไม่มีสิทธิ์การเขียนในบัฟเฟอร์พื้นฐาน (จนถึง C ++ 11; ดูความคิดเห็นของ Dietrich Epp ) คุณจะต้องทำก่อนใน c-string จากนั้นคัดลอกลงใน std :: string:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

แต่ฉันไม่แน่ใจว่าทำไมคุณไม่ใช้แค่สตรีมสตริง? ฉันสมมติว่าคุณมีเหตุผลเฉพาะที่ไม่เพียงทำ:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

17
คุกกี้เวทย์มนตร์char buf[100];ทำให้วิธีนี้ไม่แข็งแกร่งมาก แต่ความคิดที่สำคัญอยู่ที่นั่น
John Dibling

18
จอห์นสตรีมไม่ช้า เหตุผลเดียวที่ทำให้ลำธารดูเหมือนช้าคือโดยค่าเริ่มต้น iostreams กำลังซิงโครไนซ์กับเอาท์พุท C FILE เพื่อให้ cout ที่ผสมกันและ printfs เป็นเอาต์พุตที่ถูกต้อง ปิดการใช้งานลิงค์นี้ (ด้วยการเรียกไปที่ cout.sync_with_stdio (false)) ทำให้สตรีมของ c ++ มีประสิทธิภาพสูงกว่า stdio อย่างน้อยที่สุดเท่ากับ MSVC10
Jimbo

72
เหตุผลในการใช้รูปแบบคือเพื่อให้ Localizer สร้างโครงสร้างของประโยคสำหรับภาษาต่างประเทศแทนการเข้ารหัสฮาร์ดไวยากรณ์ของประโยค
Martijn Courteaux

216
ด้วยเหตุผลบางอย่างภาษาอื่น ๆ ใช้ไวยากรณ์คล้ายกับ printf: Java, Python (ไวยากรณ์ใหม่ยังคงใกล้เคียงกับ printf มากกว่าสตรีม) มีเพียงซีพลัสพลัสเท่านั้นที่ทำสิ่งที่น่ารังเกียจอย่างนี้ต่อมนุษย์ผู้บริสุทธิ์
quant_dev

9
ยิ่งไปกว่านั้นใช้asprintfซึ่งจัดสรรสตริงใหม่ที่มีพื้นที่เพียงพอที่จะเก็บผล จากนั้นคัดลอกไปที่ a std::stringหากคุณต้องการและจดจำไปfreeยังต้นฉบับ นอกจากนี้ยังเป็นไปได้ที่จะวางไว้ในแมโครเพื่อให้คอมไพเลอร์ที่ดีจะช่วยตรวจสอบรูปแบบสำหรับคุณ - คุณไม่ต้องการใส่doubleที่ที่%sคาดไว้
Aaron McDaid

287

Modern C ++ ทำให้เป็นเรื่องง่าย

C ++ 20

C ++ 20แนะนำstd::formatซึ่งช่วยให้คุณทำอย่างนั้น มันใช้เขตข้อมูลทดแทนคล้ายกับในงูหลาม :

#include <iostream>
#include <format>

int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

ตรวจสอบเอกสารฉบับเต็ม ! เป็นการปรับปรุงคุณภาพชีวิตอย่างมาก


C ++ 11

ด้วยC ++ 11 s std::snprintfสิ่งนี้กลายเป็นเรื่องง่ายและปลอดภัยแล้ว

#include <memory>
#include <string>
#include <stdexcept>

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    std::unique_ptr<char[]> buf( new char[ size ] ); 
    snprintf( buf.get(), size, format.c_str(), args ... );
    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

โค้ดข้างต้นจะได้รับใบอนุญาตภายใต้มอนส์ CC0 1.0

บรรทัดคำอธิบายบรรทัด:

AIM:เขียนไปchar*โดยใช้ แล้วแปลงที่ไปstd::snprintfstd::string

snprintfครั้งแรกที่เราตรวจสอบความยาวที่ต้องการของอาร์เรย์ถ่านโดยใช้เงื่อนไขพิเศษใน จากcppreference.com :

ค่าส่งคืน

[... ] หากสตริงผลลัพธ์ถูกตัดให้สั้นลงเนื่องจากข้อ จำกัด buf_size ฟังก์ชันจะส่งคืนจำนวนอักขระทั้งหมด (ไม่รวมถึงการยกเลิก null-byte) ซึ่งจะถูกเขียนหากขีด จำกัด ไม่ได้ถูกกำหนดไว้

ซึ่งหมายความว่าขนาดที่ต้องการคือจำนวนตัวอักษรบวกหนึ่งเพื่อให้ null-terminator จะตามหลังตัวละครอื่น ๆ ทั้งหมดและมันสามารถถูกตัดออกโดยตัวสร้างสตริงอีกครั้ง ปัญหานี้ถูกอธิบายโดย @ alexk7 ในความคิดเห็น

size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

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

if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

std::unique_ptrต่อไปเราจะจัดสรรอาร์เรย์ตัวละครใหม่และกำหนดให้ แนะนำโดยทั่วไปเนื่องจากคุณไม่จำเป็นต้องดำเนินการเองdeleteอีก

โปรดทราบว่านี่ไม่ใช่วิธีที่ปลอดภัยในการจัดสรรunique_ptrประเภทที่กำหนดโดยผู้ใช้เนื่องจากคุณไม่สามารถจัดสรรคืนหน่วยความจำได้หากตัวสร้างสร้างข้อยกเว้น!

std::unique_ptr<char[]> buf( new char[ size ] );

หลังจากนั้นเราแน่นอนก็สามารถใช้สำหรับการใช้งานที่ตั้งใจและเขียนสตริงที่จัดรูปแบบไปsnprintfchar[]

snprintf( buf.get(), size, format.c_str(), args ... );

ในที่สุดเราสร้างและส่งคืนสิ่งใหม่std::stringจากนั้นตรวจสอบให้แน่ใจว่าละเว้นโมฆะตัวสิ้นสุดในตอนท้าย

return std::string( buf.get(), buf.get() + size - 1 );

คุณสามารถดูตัวอย่างในการดำเนินการที่นี่


หากคุณต้องการใช้std::stringในรายการอาร์กิวเมนต์ให้ดูที่ส่วนสำคัญนี้


ข้อมูลเพิ่มเติมสำหรับVisual Studioผู้ใช้ :

ตามที่อธิบายไว้ในคำตอบนี้ไมโครซอฟท์เปลี่ยนชื่อstd::snprintfไป_snprintf(ใช่โดยไม่ต้องstd::) MS ตั้งค่าเพิ่มเติมเป็นเลิกใช้แล้วและแนะนำให้ใช้_snprintf_sแทน แต่_snprintf_sจะไม่ยอมรับบัฟเฟอร์ให้เป็นศูนย์หรือเล็กกว่าเอาท์พุทที่จัดรูปแบบและจะไม่คำนวณความยาวของเอาต์พุตหากเกิดขึ้น ดังนั้นเพื่อกำจัดคำเตือนการคัดค้านในระหว่างการรวบรวมคุณสามารถแทรกบรรทัดต่อไปนี้ที่ด้านบนของไฟล์ที่มีการใช้งานของ_snprintf:

#pragma warning(disable : 4996)

ความคิดสุดท้าย

คำตอบสำหรับคำถามนี้มากมายถูกเขียนขึ้นก่อนเวลาของ C ++ 11 และใช้ความยาวบัฟเฟอร์คงที่หรือ vargs ถ้าคุณไม่ติดอยู่กับ C ++ รุ่นเก่าฉันจะไม่แนะนำให้ใช้วิธีแก้ปัญหาเหล่านั้น เป็นการดีที่สุดไปทาง C ++ 20

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

หากประสิทธิภาพของพื้นที่มีความสำคัญอย่างยิ่งโซลูชันทั้งสองที่มี vargs และ vsnprintfจะมีประโยชน์ ห้ามใช้โซลูชันใด ๆ ที่มีความยาวบัฟเฟอร์คงที่ซึ่งเป็นเพียงการถามถึงปัญหา


2
กรุณาเน้นในคำตอบของคุณสำหรับผู้ใช้ Visual Studio ที่รุ่นของ VS จะต้องมีอย่างน้อยปี 2013 จากนี้บทความที่คุณจะเห็นว่ามันทำงานได้เฉพาะกับรุ่น VS2013: ถ้าบัฟเฟอร์เป็นตัวชี้โมฆะและนับเป็นศูนย์ len ถูกส่งกลับเป็น จำนวนอักขระที่ต้องใช้ในการจัดรูปแบบเอาต์พุตไม่รวมถึงการยกเลิก null หากต้องการโทรด้วยพารามิเตอร์อาร์กิวเมนต์และโลแคลที่ประสบความสำเร็จให้จัดสรรบัฟเฟอร์ที่มีตัวอักษรอย่างน้อย len + 1
cha

3
@moooeeeep หลายเหตุผล ประการแรกเป้าหมายที่นี่คือการส่งคืน std :: string ไม่ใช่ c-string ดังนั้นคุณอาจหมายถึงreturn string(&buf[0], size);หรือคล้ายกัน ประการที่สองหากคุณต้องการส่งคืน c-string เช่นนั้นมันจะทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดเนื่องจากเวกเตอร์ที่เก็บค่าที่คุณชี้ไปจะถูกยกเลิกเมื่อกลับมา ประการที่สามเมื่อฉันเริ่มเรียนรู้ C ++ มาตรฐานไม่ได้กำหนดไว้ในองค์ประกอบการสั่งซื้อที่จะต้องเก็บไว้ภายในการstd::vectorเข้าถึงหน่วยเก็บข้อมูลผ่านตัวชี้จึงเป็นพฤติกรรมที่ไม่ได้กำหนด ตอนนี้มันใช้ได้ แต่ฉันไม่เห็นประโยชน์ในการทำเช่นนั้น
iFreilicht

2
@iFreilicht ใหม่std::stringจะถูกสร้างขึ้นจากเวกเตอร์ที่แปลงโดยปริยาย (การกำหนดค่าเริ่มต้นการคัดลอก ) ซึ่งจะถูกส่งกลับเป็นสำเนาตามที่ฟังก์ชันแนะนำ นอกจากนี้องค์ประกอบของ a std::vectorและถูกจัดเก็บไว้อย่างต่อเนื่องเสมอ แต่ฉันคิดว่าคุณคงไม่มีประโยชน์อะไรในการทำเช่นนั้น
moooeeeep

4
ฉันชอบวิธีนี้มาก แต่ฉันคิดว่าบรรทัดreturn string(buf.get(), buf.get() + size);ควรเป็นreturn string(buf.get(), buf.get() + size - 1);อย่างอื่นคุณจะได้รับสตริงที่มีอักขระเป็นศูนย์ในตอนท้าย ฉันพบว่านี่เป็นกรณีของ gcc 4.9
Phil Williams

3
การส่งผ่าน std :: string ไปยัง% s ทำให้เกิดข้อผิดพลาดในการคอมไพล์ ( ข้อผิดพลาด: ไม่สามารถส่งผ่านวัตถุประเภทที่ไม่สำคัญ 'std :: __ cxx11 :: basic_string <char>' ผ่านฟังก์ชัน Variadic การโทรจะถูกยกเลิกในขณะทำงาน [-Wnon-pod -varargs] ) ใน clang 3.9.1 แต่ใน CL 19 มันคอมไพล์ได้ดีและล้มเหลวที่รันไทม์แทน ธงคำเตือนใด ๆ ที่ฉันสามารถเปิดให้มีการรวบรวมในเวลานั้นด้วย?
Zitrax

241

โซลูชัน C ++ 11 ที่ใช้vsnprintf()ภายใน:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

วิธีที่ปลอดภัยและมีประสิทธิภาพยิ่งขึ้น (ฉันทดสอบและเร็วกว่า):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

ถูกส่งผ่านโดยค่าเพื่อให้สอดคล้องกับความต้องการของfmt_strva_start

หมายเหตุ: รุ่น "ปลอดภัย" และ "เร็วขึ้น" ไม่สามารถใช้งานได้ในบางระบบ ดังนั้นทั้งสองยังคงอยู่ในรายการ นอกจากนี้ "เร็วขึ้น" จะขึ้นอยู่กับขั้นตอนการจัดสรรล่วงหน้าอย่างถูกต้องไม่เช่นstrcpyนั้นจะช้าลง


3
ช้า. ทำไมเพิ่มขนาด 1? และ funciton นี้ส่งคืน -1 เมื่อใด
0xDEAD BEEF

27
คุณเขียนทับ str.c_str () หรือไม่ มันไม่อันตรายเหรอ?
ควอนตัม

8
va_start พร้อมอาร์กิวเมนต์อ้างอิงมีปัญหากับ MSVC มันล้มเหลวอย่างเงียบ ๆ และส่งคืนพอยน์เตอร์ไปยังหน่วยความจำแบบสุ่ม สำหรับวิธีแก้ปัญหาให้ใช้ std :: string fmt แทน std :: string & fmt หรือเขียนวัตถุ wrapper
Steve Hanov

6
ฉัน +1 ทำให้ฉันรู้ว่าสิ่งนี้อาจทำงานได้โดยขึ้นอยู่กับว่า std :: ส่วนใหญ่มีการใช้งานสตริงอย่างไร c_str ไม่ได้ตั้งใจที่จะเป็นสถานที่สำหรับดัดแปลงสตริงพื้นฐาน มันควรจะเป็นแบบอ่านอย่างเดียว
Doug T.

6
และเพื่อให้ได้ความยาวสตริงที่เกิดก่อนดู: stackoverflow.com/a/7825892/908336 ฉันไม่เห็นจุดในการที่เพิ่มขึ้นในแต่ละซ้ำเมื่อคุณสามารถขอรับได้โดยสายแรกของsize vsnprintf()
Massood Khaari

107

boost::format() มีฟังก์ชั่นที่คุณต้องการ:

จากการสรุปไลบรารีรูปแบบ Boost:

วัตถุรูปแบบถูกสร้างขึ้นจากรูปแบบสตริงและจากนั้นได้รับข้อโต้แย้งผ่านการเรียกซ้ำไปยังผู้ประกอบการ แต่ละอาร์กิวเมนต์เหล่านั้นจะถูกแปลงเป็นสตริงซึ่งจะรวมกันเป็นหนึ่งสตริงตามรูปแบบสตริง

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"

5
คุณสามารถตัดห้องสมุดที่คุณต้องการได้เช่นกัน ใช้เครื่องมือที่ให้มา
Hassan Syed

7
รูปแบบ Boost ไม่เพียง แต่ใหญ่ แต่ยังช้ามาก ดูzverovich.net/2013/09/07/ …และboost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
รวมการเพิ่มที่ใดก็ได้ในโครงการของคุณเพิ่มเวลาในการรวบรวมอย่างมาก สำหรับโครงการขนาดใหญ่ส่วนใหญ่อาจไม่สำคัญ สำหรับโครงการขนาดเล็กการเพิ่มประสิทธิภาพนั้นเป็นการลาก
quant_dev

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

2
โชคไม่ดีที่รูปแบบ boost :: ไม่ทำงานเหมือนกัน: ไม่ยอมรับ var_args บางคนชอบที่จะมีรหัสทั้งหมดที่เกี่ยวข้องกับโปรแกรมเดียวที่ดูเหมือนกัน / ใช้สำนวนเดียวกัน
xor007

88

C ++ 20 จะรวมถึงรูปแบบstd::formatที่คล้ายกับsprintfAPI แต่มีความปลอดภัยแบบเต็มรูปแบบทำงานกับประเภทที่ผู้ใช้กำหนดและใช้รูปแบบสตริงรูปแบบ Python นี่คือวิธีที่คุณจะสามารถจัดรูปแบบstd::stringและเขียนลงในสตรีมได้:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

หรือ

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

หรือคุณสามารถใช้ไลบรารี {fmt}เพื่อจัดรูปแบบสตริงและเขียนลงstdoutในไฟล์หรือสตรีมไฟล์ได้ในครั้งเดียว:

fmt::print(f, "Look, a string: {}", s); // where f is a file stream

สำหรับsprintfหรือส่วนใหญ่ของคำตอบอื่น ๆ ที่นี่น่าเสียดายที่พวกเขาใช้ varargs และไม่ปลอดภัยโดยเนื้อแท้เว้นแต่ว่าคุณจะใช้formatคุณลักษณะเช่น GCC ซึ่งทำงานกับสตริงรูปแบบตัวอักษรเท่านั้น คุณสามารถดูสาเหตุที่ฟังก์ชั่นเหล่านี้ไม่ปลอดภัยในตัวอย่างต่อไปนี้:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

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

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

การปฏิเสธความรับผิดชอบ : ผมผู้เขียนของ {} fmt และ std::formatC


คุณพลาด IMHO รวม error: 'fmt' has not been declared
Sérgio

นี่เป็นเพียงตัวอย่างโค้ดไม่ใช่รหัสที่สมบูรณ์ เห็นได้ชัดว่าคุณต้องรวม <fmt / format.h> และใส่รหัสในฟังก์ชั่น
vitaut

สำหรับฉันไม่ชัดเจนดังนั้น IMHO คุณควรรวมไว้ในตัวอย่างขอขอบคุณสำหรับข้อเสนอแนะ
Sérgio

1
มีfmtการเพิ่มการใช้งานที่คล้ายกันใน C ++ 20! stackoverflow.com/a/57286312/895245 fmt ปัจจุบันอ้างว่าได้รับการสนับสนุน เยี่ยมมาก!
Ciro Santilli 郝海东冠状病六四事件法轮功

2
@vitaut ขอบคุณสำหรับการทำงานในครั้งนี้!
Curt Nichols

18

หากคุณต้องการเพียง printf เหมือนไวยากรณ์ (โดยไม่ต้องโทร printf ตัวเอง) มีลักษณะที่รูปแบบ Boost


การเพิ่มทั้งไลบรารีสำหรับสิ่งง่าย ๆ ไม่ใช่สิ่งจำเป็น นี้ได้รับการตอบที่stackoverflow.com/questions/19009094/...
Douglas Daseeco

15

ฉันเขียนของฉันเองโดยใช้ vsnprintf ดังนั้นมันจึงคืนค่าสตริงแทนที่จะต้องสร้างบัฟเฟอร์ของฉันเอง

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

ดังนั้นคุณสามารถใช้มันเหมือน

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

นี่เป็นการทำสำเนาข้อมูลแบบเต็มเป็นไปได้ที่จะใช้vsnprintfในสตริงโดยตรง
Mooing Duck

1
ใช้รหัสในstackoverflow.com/a/7825892/908336เพื่อรับความยาวสตริงที่เกิดก่อน และคุณสามารถใช้ตัวชี้อัจฉริยะเพื่อรับรหัสที่ปลอดภัยยกเว้น:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

ฉันไม่แน่ใจว่าถูกต้องในกรณีสำรอง; ฉันคิดว่าคุณต้องทำ va_copy ของ vl สำหรับ vsnprintf ที่สอง () เพื่อดูอาร์กิวเมนต์อย่างถูกต้อง ดูตัวอย่างได้ที่: github.com/haberman/upb/blob/…
Josh Haberman

15

เพื่อจัดรูปแบบstd::stringในลักษณะ 'sprintf' ให้เรียกใช้snprintf(อาร์กิวเมนต์nullptrและ0) เพื่อรับความยาวของบัฟเฟอร์ที่ต้องการ เขียนฟังก์ชันของคุณโดยใช้เทมเพลตชุดคำสั่ง C ++ 11 เช่นนี้:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return str;
}

คอมไพล์ด้วยการสนับสนุน C ++ 11 ตัวอย่างเช่นใน GCC: g++ -std=c++11

การใช้งาน:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

std :: snprintf ไม่พร้อมใช้งานใน VC ++ 12 (Visual Studio 2013) แทนที่ด้วย _snprintf แทน
Shital Shah

ทำไมคุณไม่ได้ใช้char buf[length + 1];แทนchar* buf = new char[length + 1];?
Behrouz.M

ความแตกต่างระหว่างการใช้char[]และchar*กับใหม่คือในกรณีก่อน buf จะถูกจัดสรรในกอง มันก็โอเคสำหรับบัฟเฟอร์เล็ก ๆ แต่เนื่องจากเราไม่สามารถรับประกันขนาดของสตริงที่ได้จึงเป็นการดีกว่าการใช้newเล็กน้อย ตัวอย่างเช่นบนเครื่องของฉันstring_sprintf("value: %020000000d",5)ให้พิมพ์เลขศูนย์นำหน้าเลข 5 อย่างไม่เหมาะสมจำนวนหลักทิ้งเมื่อใช้อาร์เรย์บนสแต็ก แต่ทำงานตกลงเมื่อใช้อาร์เรย์ที่จัดสรรแบบไดนามิกnew char[length + 1]
2622016

แนวคิดที่ฉลาดมากที่จะได้รับขนาดบัฟที่แท้จริงสำหรับเอาท์พุทที่จัดรูปแบบ
Chris

1
@ user2622016: ขอบคุณสำหรับการแก้ปัญหา! โปรดทราบว่าเป็นฟุ่มเฟือยstd::move
หมดเวลา Todor

14

[แก้ไข: 20/05/25] ยังดีกว่า ... :
ในส่วนหัว:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

PRINTSTRING(r)ฟังก์ชั่คือการตอบสนองสำหรับ GUI หรือขั้วหรือความต้องการพิเศษการส่งออกโดยใช้#ifdef _some_flag_ค่าเริ่มต้นคือ:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[แก้ไข '17 / 8/31] การเพิ่มเวอร์ชันเทมเพลตตัวแปร 'vtspf (.. )':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

ซึ่งเป็นเวอร์ชันที่คั่นด้วยเครื่องหมายจุลภาค (แทน) อย่างมีประสิทธิภาพของผู้<<ปฏิบัติการที่ขัดขวางบางครั้งใช้แบบนี้:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


ดัดแปลงเพื่อใช้ประโยชน์จากเทคนิคในคำตอบของ Erik Aronesty (ด้านบน):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[คำตอบก่อนหน้านี้] คำตอบ
ที่ช้ามาก แต่สำหรับคนที่ชอบฉันทำอย่าง 'sprintf' ทาง: ฉันได้เขียนและใช้ฟังก์ชั่นต่อไปนี้ หากคุณชอบคุณสามารถขยาย% -options เพื่อให้พอดีกับ sprintf สิ่งที่อยู่ในนั้นเพียงพอสำหรับความต้องการของฉัน คุณใช้ stringf () และ stringfappend () เหมือนกับที่คุณใช้ sprintf เพียงจำไว้ว่าพารามิเตอร์สำหรับ ... ต้องเป็นประเภท POD

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}

@MooingDuck: เปลี่ยนพารามิเตอร์ฟังก์ชันตามความคิดเห็นของ Dan ต่อคำตอบของ Aronesty ฉันใช้เฉพาะ Linux / gcc เท่านั้นและด้วยfmtการอ้างอิงมันใช้งานได้ดี (แต่ฉันคิดว่าคนจะต้องการเล่นกับของเล่นดังนั้น ... ) หากมีข้อผิดพลาดอื่น ๆ ที่คุณสามารถโปรดอธิบายอย่างละเอียด?
slashmais

ฉันเข้าใจผิดว่าโค้ดส่วนหนึ่งของเขาทำงานอย่างไรและคิดว่ามันทำเพื่อปรับขนาดจำนวนมาก การรีไซมินนิ่งแสดงให้เห็นว่าฉันเข้าใจผิด รหัสของคุณถูกต้อง
Mooing Duck

การสร้างคำตอบของ Erik Aronesty คือปลาเฮอริ่งแดง ตัวอย่างโค้ดแรกของเขาไม่ปลอดภัยและที่สองของเขาไม่มีประสิทธิภาพและเงอะงะ การใช้งานที่สะอาดถูกระบุไว้อย่างชัดเจนโดยข้อเท็จจริงที่ว่าถ้า buf_siz ของฟังก์ชัน vprintf ใด ๆ ในตระกูลเป็นศูนย์จะไม่มีการเขียนอะไรและบัฟเฟอร์อาจเป็นตัวชี้ที่เป็นโมฆะอย่างไรก็ตามค่าส่งคืน (จำนวนไบต์ที่จะเขียนไม่รวมถึง ตัวยกเลิก null) ยังคงคำนวณและส่งคืน คำตอบคุณภาพการผลิตอยู่ที่นี่: stackoverflow.com/questions/2342162/…
Douglas Daseeco

10

นี่คือวิธีที่ Google ทำ: StringPrintf(BSD License)
และ Facebook ทำในลักษณะที่คล้ายกันมาก: StringPrintf(Apache License)
ทั้งสองอย่างให้ความสะดวกStringAppendFเช่นกัน


10

สองเซ็นต์ของฉันสำหรับคำถามยอดนิยมนี้

ในการอ้างอิงฟังก์ชั่นmanpage ของprintf-like :

เมื่อส่งคืนสำเร็จฟังก์ชันเหล่านี้จะคืนค่าจำนวนอักขระที่พิมพ์ (ยกเว้นไบต์ null ที่ใช้เพื่อสิ้นสุดเอาต์พุตไปยังสตริง)

ฟังก์ชั่น snprintf () และ vsnprintf () ไม่ได้เขียนมากกว่าขนาดไบต์ (รวมถึงการยกเลิก null ไบต์ ('\ 0')) หากเอาต์พุตถูกตัดให้สั้นลงเนื่องจากข้อ จำกัด นี้ค่าส่งคืนคือจำนวนอักขระ (ไม่รวมไบต์ null ที่สิ้นสุด) ซึ่งจะถูกเขียนไปยังสตริงสุดท้ายหากมีพื้นที่ว่างเพียงพอ ดังนั้นค่าส่งคืนของขนาดหรือมากกว่าหมายความว่าเอาท์พุทถูกตัดทอน

กล่าวอีกนัยหนึ่งการใช้งาน C ++ 11 อย่างมีเหตุผลควรเป็นดังนี้:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}

มันใช้งานได้ดี :)

เทมเพลต Variadic ได้รับการสนับสนุนใน C ++ 11 เท่านั้น คำตอบจาก pixelpoint แสดงเทคนิคที่คล้ายกันโดยใช้รูปแบบการเขียนโปรแกรมแบบเก่า

มันแปลกที่ C ++ ไม่มีสิ่งเช่นนี้ออกมาจากกล่อง พวกเขาเพิ่มเมื่อเร็ว ๆ นี้to_string()ซึ่งในความคิดของฉันเป็นขั้นตอนที่ดีไปข้างหน้า ฉันสงสัยว่าพวกเขาจะเพิ่ม.formatโอเปอเรเตอร์ให้กับstd::stringในที่สุด ...

แก้ไข

ตามที่ alexk7 ชี้ให้เห็น+1จำเป็นต้องใช้ค่าส่งคืนของstd::snprintfเนื่องจากเราจำเป็นต้องมีพื้นที่สำหรับ\0ไบต์ สังหรณ์ใจในสถาปัตยกรรมส่วนใหญ่ที่หายไป+1จะทำให้เกิดการจำนวนเต็มถูกเขียนทับบางส่วนด้วยrequired 0สิ่งนี้จะเกิดขึ้นหลังจากการประเมินrequiredเป็นพารามิเตอร์จริงสำหรับstd::snprintfดังนั้นไม่ควรมองเห็นผลกระทบ

อย่างไรก็ตามปัญหานี้สามารถเปลี่ยนแปลงได้ตัวอย่างเช่นคอมไพเลอร์ปรับให้เหมาะสม: ถ้าคอมไพเลอร์ตัดสินใจที่จะใช้การลงทะเบียนสำหรับrequiredตัวแปร? นี่เป็นข้อผิดพลาดชนิดหนึ่งซึ่งบางครั้งส่งผลให้เกิดปัญหาด้านความปลอดภัย


1
snprintf จะผนวก null-terminate ที่ต่อท้ายเสมอ แต่ส่งคืนจำนวนอักขระที่ไม่มี รหัสนี้ไม่ข้ามอักขระตัวสุดท้ายเสมอหรือ
alexk7

@ alexk7 จับได้ดี! ฉันกำลังปรับปรุงคำตอบ รหัสไม่ได้ข้ามตัวอักษรตัวสุดท้าย แต่เขียนเกินจุดสิ้นสุดของbytesบัฟเฟอร์อาจมากกว่าrequiredจำนวนเต็ม (ซึ่งโชคดีที่จุดนั้นได้รับการประเมินแล้ว)
Dacav

1
เพียงคำใบ้เล็กน้อย: ด้วยขนาดบัฟเฟอร์ที่ 0 คุณสามารถส่ง a nullptrเป็นอาร์กิวเมนต์ของบัฟเฟอร์ได้โดยกำจัดchar b;บรรทัดในโค้ดของคุณ ( ที่มา )
iFreilicht

@iFreilicht, fix'd +1 อีกด้วย
Dacav

2
การใช้ "char bytes [จำเป็น]" จะจัดสรรให้กับสแต็กแทนที่จะเป็นฮีปซึ่งอาจเป็นอันตรายต่อสตริงรูปแบบขนาดใหญ่ ลองใช้ใช้ใหม่แทน Yann
Yannuth

9
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

ใช้ C99 snprintf และ C ++ 11


9

ผ่านการทดสอบคำตอบคุณภาพการผลิต

คำตอบนี้จัดการกรณีทั่วไปด้วยเทคนิคที่เป็นไปตามมาตรฐาน วิธีการเดียวกันนี้ให้ไว้เป็นตัวอย่างในCppReference.comใกล้กับด้านล่างของหน้า รหัสนี้เหมาะกับความต้องการของคำถามและมีการทดสอบภาคสนามในหุ่นยนต์และแอปพลิเคชั่นดาวเทียม นอกจากนี้ยังมีการปรับปรุงการแสดงความคิดเห็น คุณภาพการออกแบบจะกล่าวถึงต่อไปด้านล่าง

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

ประสิทธิภาพเชิงเส้นที่ทำนายได้

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

การลองซ้ำมากเกินไปจะไม่มีประสิทธิภาพแบบเอ็กซ์โปเนนเชียลซึ่งเป็นอีกเหตุผลที่กล่าวถึงเมื่อคณะกรรมการมาตรฐาน C ++ 11 กล่าวถึงข้อเสนอข้างต้นเพื่อให้การทำงานแบบแห้งเมื่อบัฟเฟอร์การเขียนเป็นโมฆะ

ในการดำเนินการพร้อมการผลิตข้างต้นการดำเนินการครั้งแรกจะเป็นการดำเนินการแบบแห้งเพื่อกำหนดขนาดการปันส่วน ไม่มีการจัดสรรเกิดขึ้น การแยกคำสั่ง printf และการอ่าน vargs นั้นมีประสิทธิภาพอย่างมากในช่วงหลายทศวรรษที่ผ่านมา รหัสที่ใช้ซ้ำได้ควรคาดเดาได้แม้ว่าจะต้องเสียสละเล็ก ๆ น้อย ๆ สำหรับกรณีเล็ก ๆ น้อย ๆ

ความปลอดภัยและความน่าเชื่อถือ

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

นี้ถูกกล่าวถึงในข้อเสนออย่างเป็นทางการมาตรฐานการแก้ไขสำหรับคุณลักษณะบัฟเฟอร์โมฆะในทางเลือกที่จะ sprintf, C9X การทบทวนข้อเสนอ , ISO IEC เอกสาร WG14 N645 / X3J11 96-008 สตริงที่มีความยาวตามอำเภอใจแทรกตามคำสั่งการพิมพ์ "% s," ภายใต้ข้อ จำกัด ของความพร้อมใช้งานของหน่วยความจำแบบไดนามิกไม่ใช่ข้อยกเว้นและไม่ควรใช้ประโยชน์ในการผลิต

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

นอกจากนี้การทดสอบกรณีความล้มเหลวนั้นไม่ค่อยประสบความสำเร็จในกรณีที่ประสบความสำเร็จ

ความเบา

ผู้จำหน่ายระบบปฏิบัติการรายใหญ่ทั้งหมดเตรียมคอมไพเลอร์ที่รองรับ std :: vsnprintf อย่างสมบูรณ์ซึ่งเป็นส่วนหนึ่งของมาตรฐาน c ++ 11 โฮสต์ที่ใช้งานผลิตภัณฑ์ของผู้ขายที่ไม่รักษาการกระจายสินค้าควรตกแต่งด้วย g ++ หรือ clang ++ ด้วยเหตุผลหลายประการ

ใช้กอง

การใช้สแต็กในการโทรที่ 1 ไปยัง std :: vsnprintf จะน้อยกว่าหรือเท่ากับการโทรที่ 2 และมันจะถูกปล่อยให้เป็นอิสระก่อนที่การโทรครั้งที่ 2 จะเริ่มขึ้น หากการเรียกครั้งแรกเกินความพร้อมใช้งานของสแต็ก std :: fprintf ก็จะล้มเหลวเช่นกัน


สั้น ๆ และแข็งแกร่ง มันอาจล้มเหลวใน HP-UX, IRIX, Tru64 ซึ่งไม่สอดคล้องกับ vsnprintf-s แก้ไข: นอกจากนี้เมื่อพิจารณาว่าสองรอบอาจส่งผลต่อการแสดงอย่างไร สำหรับการจัดรูปแบบสตริงทั่วไปส่วนใหญ่คุณคิดว่าการเดาพาสครั้งแรกนั้นอาจมีขนาดใหญ่เพียงพอหรือไม่?
วิศวกร

FWIW การคาดเดาที่ฉันอ้างถึงใช้บัฟเฟอร์สแต็กที่จัดสรรซึ่งมีการรันครั้งแรกเกิดขึ้น หากเหมาะสมจะช่วยประหยัดค่าใช้จ่ายของการรันครั้งที่สองและการจัดสรรแบบไดนามิกที่เกิดขึ้นที่นั่น สันนิษฐานว่าสตริงขนาดเล็กมีการใช้บ่อยกว่าสตริงขนาดใหญ่ ในเกณฑ์มาตรฐานที่หยาบของฉันว่ากลยุทธ์ (เกือบ) จะแบ่งครึ่งเวลาทำงานสำหรับสตริงขนาดเล็กและอยู่ภายในไม่กี่เปอร์เซ็นต์ (อาจจะเป็นค่าใช้จ่ายคงที่?) ของกลยุทธ์ข้างต้น คุณช่วยอธิบายรายละเอียดเกี่ยวกับการออกแบบ C ++ 11 ที่ใช้งานแบบระยะใกล้ได้หรือไม่? ฉันต้องการอ่านเกี่ยวกับเรื่องนี้
วิศวกร

@ นักวิศวกรรมคำถามของคุณได้รับการตอบในเนื้อความของคำตอบด้านบนและด้านล่างของรหัส หัวข้อย่อยสามารถทำให้อ่านง่ายขึ้น
Douglas Daseeco

6

C ++ 20 std::format

มันมาถึงแล้ว! คุณลักษณะนี้มีอธิบายอยู่ที่: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.htmlและใช้ Python-like.format()ไวยากรณ์

ฉันคาดหวังว่าการใช้งานจะเป็นเช่น:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

ฉันจะลองดูเมื่อการสนับสนุนมาถึง GCC, GCC 9.1.0 ด้วย g++-9 -std=c++2aยังไม่รองรับ

API จะเพิ่มstd::formatส่วนหัวใหม่:

API การจัดรูปแบบที่เสนอถูกกำหนดไว้ในส่วนหัวใหม่<format>และไม่ควรส่งผลกระทบต่อโค้ดที่มีอยู่

fmtห้องสมุดที่มีอยู่อ้างว่าจะใช้มันหากคุณต้องการ polyfill: https://github.com/fmtlib/fmt

การดำเนินงานของ std::formatC

และถูกกล่าวถึงก่อนหน้านี้ที่: std :: การจัดรูปแบบสตริงเช่น sprintf


5

ตามคำตอบของ Erik Aronesty:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}

นี่เป็นการหลีกเลี่ยงความจำเป็นที่จะต้องละทิ้งconstผล.c_str()ที่ได้จากคำตอบเดิม


1
การสร้างคำตอบของ Erik Aronesty คือปลาเฮอริ่งแดง ตัวอย่างโค้ดแรกของเขาไม่ปลอดภัยและที่สองของเขาด้วยลูปนั้นไม่มีประสิทธิภาพและเงอะงะ การใช้งานที่สะอาดถูกระบุไว้อย่างชัดเจนโดยข้อเท็จจริงที่ว่าถ้า buf_siz ของฟังก์ชัน vprintf ใด ๆ ในตระกูลเป็นศูนย์จะไม่มีการเขียนอะไรและบัฟเฟอร์อาจเป็นตัวชี้ที่เป็นโมฆะอย่างไรก็ตามค่าส่งคืน (จำนวนไบต์ที่จะเขียนไม่รวมถึง ตัวยกเลิก null) ยังคงคำนวณและส่งคืน คำตอบคุณภาพการผลิตอยู่ที่นี่: stackoverflow.com/questions/2342162/…
Douglas Daseeco

คำตอบของ Erik Aronesty ได้รับการแก้ไขตั้งแต่มีการเพิ่มของฉัน ฉันต้องการเน้นตัวเลือกในการใช้ vector <char> เพื่อจัดเก็บสตริงตามที่สร้างขึ้น ฉันใช้เทคนิคนี้บ่อยครั้งเมื่อเรียกใช้ฟังก์ชัน C จากรหัส C ++ เป็นที่น่าสนใจว่าคำถามนี้มี 34 คำตอบ
ChetS

ตัวอย่าง cppreference.com บนหน้า vfprintf ถูกเพิ่มในภายหลัง ฉันเชื่อว่าคำตอบที่ดีที่สุดคือคำตอบที่ได้รับการยอมรับในขณะนี้การใช้สตริงสตรีมแทนตัวแปร printf คือวิธี C ++ อย่างไรก็ตามคำตอบของฉันได้เพิ่มมูลค่าเมื่อมันถูกจัดเตรียมไว้; ในเวลานั้นมันดีกว่าคำตอบอื่น ๆ ตอนนี้มาตรฐานมี string_view, ชุดพารามิเตอร์และแม่แบบ Variadic คำตอบใหม่อาจรวมถึงคุณสมบัติเหล่านั้น สำหรับคำตอบของฉันแม้ว่ามันอาจไม่สมควรได้รับการโหวตเพิ่มเติมอีกต่อไป แต่ก็ไม่สมควรที่จะถูกลบหรือลงคะแนนดังนั้นฉันจึงทิ้งมันไว้
ChetS

5
inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}

1
+1 สำหรับความคิดที่ชาญฉลาด แต่มันไม่ชัดเจนว่า_vscprintfมันคืออะไร ฉันคิดว่าคุณควรทำอย่างละเอียดในคำตอบนี้
Dacav

3

สตริงไม่มีสิ่งที่คุณต้องการ แต่ std :: stringstream ใช้สตริงสตรีมเพื่อสร้างสตริงจากนั้นแตกสตริง นี่คือรายการที่ครอบคลุมเกี่ยวกับสิ่งที่คุณสามารถทำได้ ตัวอย่างเช่น:

cout.setprecision(10); //stringstream is a stream like cout

จะให้ความแม่นยำทศนิยม 10 ตำแหน่งเมื่อพิมพ์คู่หรือทศนิยม


8
ซึ่งยังไม่ให้อะไรเลยใกล้กับ printf ให้คุณ ... แต่ก็ดี
Erik Aronesty

3

คุณสามารถลองสิ่งนี้:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );

3

หากคุณอยู่ในระบบที่มีasprintf (3)คุณสามารถห่อได้อย่างง่ายดาย:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}

2
ฉันจะเพิ่มบรรทัดนี้เป็นการประกาศก่อนหน้าformatนี้เพราะมันบอก gcc เพื่อตรวจสอบประเภทของข้อโต้แย้งและให้คำเตือนที่เหมาะสมกับ - วอลล์:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
va_endฉันได้เพิ่มเพียงการเรียกร้องให้ "ถ้า va_end ไม่ถูกเรียกก่อนฟังก์ชันที่เรียก va_start หรือ va_copy ส่งคืนพฤติกรรมจะไม่ได้กำหนด" - docs
Aaron McDaid

1
คุณควรตรวจสอบผลลัพธ์ส่งคืนของ vasprintf เนื่องจากค่าตัวชี้ไม่ได้ถูกกำหนดเมื่อล้มเหลว ดังนั้นอาจรวม <new> และเพิ่ม: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

จุดดีฉันได้แก้ไขคำตอบตามนั้นฉันตัดสินใจที่จะใส่ความคิดเห็นที่นั่นแทนที่จะทำthrow std::bad_alloc();เพราะฉันไม่ได้ใช้ข้อยกเว้น C ++ ใน codebase ของฉันและสำหรับผู้ที่ทำเช่นนั้นพวกเขาสามารถเพิ่มได้อย่างง่ายดายตาม บนความคิดเห็นต้นฉบับและความคิดเห็นของคุณที่นี่
โทมัส Perl

2

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

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}

4
การเริ่มต้นของ textString ตั้งบัฟเฟอร์ทั้งหมดเป็นศูนย์แล้ว ไม่จำเป็นต้องจำ ...
EricSchaefer

นี่เป็นการทำสำเนาข้อมูลแบบเต็มเป็นไปได้ที่จะใช้vsnprintfในสตริงโดยตรง
Mooing Duck

2

เอาความคิดจากDacavและpixelpoint ของคำตอบ ฉันเล่นไปรอบ ๆ และได้รับสิ่งนี้:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

ด้วยการฝึกฝนการเขียนโปรแกรมที่มีเหตุผลฉันเชื่อว่ารหัสควรจะเพียงพอ แต่ฉันยังคงเปิดให้กับทางเลือกที่ปลอดภัยกว่าซึ่งยังคงง่ายพอและไม่ต้องการ C ++ 11


และนี่คืออีกเวอร์ชั่นที่ใช้บัฟเฟอร์เริ่มต้นเพื่อป้องกันการเรียกครั้งที่สองvsnprintf()เมื่อบัฟเฟอร์เริ่มต้นเพียงพอแล้ว

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

( แต่กลับกลายเป็นว่ารุ่นนี้เป็นเพียงคล้ายกับคำตอบปิติ Ongmongkolkul ของเพียงว่ามันไม่ได้ใช้newและและยังระบุขนาดเมื่อสร้าง delete[]std::string

แนวคิดในการไม่ใช้งานnewและdelete[]นี่ก็หมายถึงการใช้งานสแต็คมากกว่าฮีปเนื่องจากไม่จำเป็นต้องเรียกฟังก์ชันการจัดสรรและการจัดสรรคืนอย่างไรก็ตามหากไม่ได้ใช้อย่างถูกต้องอาจเป็นอันตรายต่อบัฟเฟอร์ล้นในบางอย่าง (อาจเก่าหรือ อาจเป็นเพียงช่องโหว่) ระบบ หากนี่เป็นข้อกังวลฉันขอแนะนำให้ใช้newและdelete[]แทน โปรดทราบว่าข้อกังวลเพียงอย่างเดียวในที่นี้คือเกี่ยวกับการจัดสรรตามที่vsnprintf()เรียกใช้แล้วพร้อมกับข้อ จำกัด ดังนั้นการระบุข้อ จำกัด ตามขนาดที่จัดสรรบนบัฟเฟอร์ที่สองจะป้องกันสิ่งเหล่านั้นด้วย)


2

ฉันมักจะใช้สิ่งนี้:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

ข้อเสีย: ระบบทั้งหมดไม่สนับสนุน vasprint


vasprintf นั้นดี แต่คุณต้องตรวจสอบรหัสส่งคืน ในบัฟเฟอร์ -1 จะมีค่าไม่ได้กำหนด ต้องการ: ถ้า (size == -1) {throw std :: bad_alloc (); }
Neil McGill

2

รุ่น @iFreilicht ที่ได้รับการแก้ไขด้านล่างเล็กน้อยได้รับการอัปเดตเป็นC ++ 14 (การใช้งานของmake_uniqueฟังก์ชั่นแทนการประกาศแบบดิบ) และเพิ่มการรองรับstd::stringอาร์กิวเมนต์ (ตามบทความ Kenny Kerr )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

เอาท์พุท:

i = 3, f = 5.000000, s = hello world

รู้สึกอิสระที่จะรวมคำตอบนี้กับคำตอบเดิมหากต้องการ



1

คุณสามารถจัดรูปแบบ C ++ เอาต์พุตใน cout โดยใช้ไฟล์ส่วนหัว iomanip ตรวจสอบให้แน่ใจว่าคุณใส่ไฟล์ส่วนหัว iomanip ก่อนที่คุณจะใช้ฟังก์ชันตัวช่วยใด ๆ เช่น setprecision, setfill เป็นต้น

นี่คือข้อมูลโค้ดที่ฉันใช้ในอดีตเพื่อพิมพ์เวลารอเฉลี่ยในเวกเตอร์ซึ่งฉันมี "สะสม"

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

นี่คือคำอธิบายสั้น ๆ เกี่ยวกับวิธีจัดรูปแบบสตรีม C ++ http://www.cprogramming.com/tutorial/iomanip.html


1

อาจมีปัญหาหากบัฟเฟอร์ไม่ใหญ่พอที่จะพิมพ์สตริง คุณต้องกำหนดความยาวของสตริงที่จัดรูปแบบก่อนที่จะพิมพ์ข้อความที่จัดรูปแบบในนั้น ฉันทำผู้ช่วยของตัวเอง (ทดสอบบน Windows และ Linux GCC ) และคุณสามารถลองใช้งานได้

String.cpp: http://pastebin.com/DnfvzyKP
String.h: http://pastebin.com/7U6iCUMa

String.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

สตริง:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

เกี่ยวกับสายvsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- มีความปลอดภัยหรือไม่ที่จะถือว่าบัฟเฟอร์ของสตริงมีที่ว่างสำหรับการยกเลิกอักขระ null? มีการใช้งานที่ไม่ได้จัดสรรขนาด + 1 ตัวอักษร จะปลอดภัยกว่าไหมถ้าทำdst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

เห็นได้ชัดว่าคำตอบสำหรับความคิดเห็นก่อนหน้าของฉันคือ: ไม่ปลอดภัยที่จะถือว่ามีอักขระเป็นโมฆะ โดยเฉพาะเกี่ยวกับข้อมูลจำเพาะ C ++ 98: "การเข้าถึงค่าที่ data () + size () สร้างลักษณะการทำงานที่ไม่ได้กำหนด : ไม่มีการรับประกันว่าอักขระ null จะยุติลำดับอักขระที่ชี้โดยค่าที่ส่งคืนโดยฟังก์ชันนี้ดูสตริง :: c_str สำหรับฟังก์ชั่นที่ให้การรับประกันดังกล่าว โปรแกรมจะไม่เปลี่ยนแปลงอักขระใด ๆ ในลำดับนี้ "อย่างไรก็ตามข้อมูลจำเพาะ C ++ 11 ระบุว่าdataและc_strเป็นคำพ้องความหมาย
drwatsoncode



1

ฉันรู้ว่าคำตอบนี้ได้รับการตอบแล้วหลายครั้ง แต่นี่กระชับกว่า:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

ตัวอย่าง:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

ดูเพิ่มเติมที่http://rextester.com/NJB14150


1

UPDATE 1 : fmt::formatการทดสอบเพิ่มเติม

ฉันได้ทำการตรวจสอบวิธีการของฉันเองที่นี่และได้รับผลลัพธ์ตรงข้ามกับที่กล่าวถึงที่นี่

ฉันได้ใช้ 4 ฟังก์ชั่นใน 4 วิธี:

  • ฟังก์ชัน Variadic + vsnprintf+std::unique_ptr
  • ฟังก์ชัน Variadic + vsnprintf+std::string
  • ฟังก์ชันเท็มเพลต Variadic + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatฟังก์ชั่นจากfmtห้องสมุด

สำหรับแบ็กเอนด์ทดสอบที่googletestได้ใช้

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

การfor_eachดำเนินการถูกนำมาจากที่นี่: ทำซ้ำมากกว่า tuple

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

การทดสอบ:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unsued.hpp :

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

unused.cpp :

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

ผลลัพธ์ :

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

ดังที่คุณเห็นการใช้งานผ่านvsnprintf+ std::stringเท่ากับfmt::formatแต่เร็วกว่าผ่านvsnprintf+ std::unique_ptrซึ่งเร็วกว่าการใช้std::ostringstreamใช้

การทดสอบการรวบรวมและการทำงานที่Visual Studio 2015 Update 3Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB

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