std :: sprintfのような文字列フォーマット


454

でフォーマットstd::stringsprintfてファイルストリームに送信する必要があります。これどうやってするの?


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 ++ストリームとうまく統合されています。
Trevor Boyd Smith、

@Ockonal —コミュニティのために(担当者のことはあまり気にしなかった)選択を変更することをお勧めします。現在選択されているものは、最初のスニペットで、任意の最大長の使用で発生するのを待っているバグを示しています。2番目のスニペットは、sprintfのようなvargsを使用するというあなたの表明した欲求を完全に無視します。ここでは、クリーンで安全で、C ++標準のみに依存し、テストされ、よくコメントされている唯一の回答を選択することをお勧めします。それが私のものであることは関係ありません。それは客観的に真実です。stackoverflow.com/questions/2342162/…を参照してください。
ダグラスダシーコ

@TrevorBoydSmith a std::formatがC ++ 20 BTWに追加されました:stackoverflow.com/a/57286312/895245素晴らしい!
Ciro Santilli郝海东冠状病六四事件法轮功

1
@CiroSantilli C++20昨日の記事を読んだところ、仕様にを追加することで(今では100万回目も)C++20コピーされたことがわかりました!とても幸せでした!私が過去9年間に書いたほとんどすべてのC ++ファイルが使用しました。C ++のストリームに公式のprintfスタイルの出力を追加すると、すべてのC ++でIMOに大きな効果があります。booststd::formatC++20boost::format
Trevor Boyd Smith、

回答:


333

基礎となるバッファーへの書き込みアクセス権がないため、直接実行することはできません(C ++ 11まで。DietrichEppのコメントを参照)。まず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
の魔法のcookieはchar buf[100];、このソリューションをあまり堅牢にしません。しかし、本質的なアイデアはそこにあります。
John Dibling 2010

18
ジョン、ストリームは遅くありません。ストリームが遅く見える唯一の理由は、デフォルトではiostreamがC FILE出力と同期しているため、混合したcoutとprintfsが正しく出力されるためです。このリンクを無効にすると(cout.sync_with_stdio(false)を呼び出して)、c ++のストリームのパフォーマンスが、少なくともMSVC10以降のstdioよりも高くなります。
神保

72
フォーマットを使用する理由は、ローカライザに、文の文法をハードコーディングする代わりに、外国語の文の構造を再構築させるためです。
Martijn Courteaux 2013

216
何らかの理由で、他の言語はJava、Pythonなどのprintfに似た構文を使用します(新しい構文は、ストリームよりもprintfに近いです)。C ++だけが無実の人間にこの冗長な嫌悪感を与えます。
quant_dev

9
さらに良いのはasprintf、結果を保持するのに十分なスペースを持つ新しい文字列を割り当てるを使用することです。次に、std::string必要に応じてそれをにコピーしfree、オリジナルを覚えておいてください。また、これをマクロに入れて、適切なコンパイラーがフォーマットを検証するのに役立つようにすることもできます- 期待されるdouble場所にA を入れたくない%s
Aaron McDaid

286

最新のC ++はこれを非常に簡単にします。

C ++ 20

C ++ 20にはが導入されてstd::formatいます。これにより、まさにそれが可能になります。pythonと同様の置換フィールドを使用します。

#include <iostream>
#include <format>

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

完全なドキュメントをチェックしてください!これは、生活の質を大幅に改善するものです。


C ++ 11

C ++ 11std::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でライセンスされています。

行ごとの説明:

目的:char*使用 してaに書き込みstd::snprintf、それをaに変換しstd::stringます。

まず、の特別な条件を使用して、char配列の必要な長さを決定しますsnprintfcppreference.comから:

戻り値

[...]結果の文字列がbuf_sizeの制限により切り捨てられた場合、関数は、制限が課されなかった場合に書き込まれたであろう合計文字数(終端のnullバイトを含まない)を返します。

これは、必要なサイズが文字数+ 1であることを意味します。これにより、null終止符は他のすべての文字の後に置かれ、文字列コンストラクターによって再び切り捨てることができます。この問題は、コメントで@ 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 ] );

その後はもちろんsnprintf、その意図した用途に使用して、フォーマットされた文字列をに書き込むことができますchar[]

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

最後に、std::stringそこから新しいを作成して返します。最後にnullターミネータを必ず省略してください。

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

ここで実際の例を見ることができます


std::string引数リストでも使用したい場合は、この要点をご覧ください


Visual Studioユーザー向けの追加情報:

この回答で説明したように、Microsoftの名前std::snprintf_snprintf(はい、なしstd::)に変更されました。MSはさらにそれを非推奨として設定し、_snprintf_s代わりに使用することをお勧めしますが_snprintf_s、バッファーをゼロまたはフォーマットされた出力より小さくすることは受け入れず、それが発生した場合、出力長を計算しません。したがって、コンパイル中に非推奨の警告を取り除くために、次の行をファイルの先頭に挿入し、の使用を含めることができます_snprintf

#pragma warning(disable : 4996)

最終的な考え

この質問に対する多くの回答はC ++ 11の時代より前に書かれており、固定長のバッファまたは変数を使用しています。古いバージョンのC ++に悩まされない限り、これらのソリューションの使用はお勧めしません。理想的には、C ++ 20の方法を使用します。

この回答のC ++ 11ソリューションはテンプレートを使用しているため、頻繁に使用するとかなりの量のコードが生成される可能性があります。ただし、バイナリ用のスペースが非常に限られている環境用に開発しているのでない限り、これは問題にならず、明快さとセキュリティの両面で他のソリューションよりも大幅に改善されます。

スペース効率が非常に重要な場合は、vargsとvsnprintfを使用したこれらの2つのソリューションが役立ちます。 バッファの長さが固定されているソリューションは使用しないでください。問題が発生するだけです。


2
VSのバージョンは2013以降である必要があることをVisual Studioユーザーへの回答で強調してください。この記事から、それがVS2013バージョンでのみ機能することがわかります:バッファーがnullポインターでカウントがゼロの場合、lenは次のように返されます出力のフォーマットに必要な文字数。終端のnullは含まれません。同じ引数とロケールパラメータで呼び出しを成功させるには、少なくともlen + 1文字を保持するバッファを割り当てます。
2015年

3
@moooeeeep複数の理由。まず、ここでの目標は、c文字列ではなくstd :: stringを返すことですreturn string(&buf[0], size);。次に、そのようなc文字列を返すと、ポイントすると、指定した値を保持するベクトルが戻り時に無効になるため、未定義の動作が発生します。3番目に、C ++の学習を始めたとき、標準は要素を内に格納する必要がある順序を定義していなかったためstd::vector、ポインタを介してそのストレージにアクセスすることは未定義の動作でした。これでうまくいきますが、そのようにしてもメリットはありません。
iFreilicht 2015

2
@iFreilicht 関数シグネチャが示すようにstd::string、暗黙的に変換されたベクトル(コピーの初期化)から新しいものが作成され、コピーとして返されます。また、aの要素は、連続してstd::vector格納されます。しかし、そうすることには何の利益もないかもしれないと私はあなたの主張をします。
moooeeeep 2015

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>'のオブジェクトを可変個の関数を通じて渡すことができません。呼び出しは実行時に中止されます[-Wnon-pod -varargs])はclang 3.9.1で使用されていましたが、CL 19では正常にコンパイルされ、実行時にクラッシュします。コンパイル時にclでも呼び出されるようにするためにオンにできる警告フラグはありますか?
Zitrax 2017

241

vsnprintf()内部で使用するC ++ 11ソリューション:

#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_strの要件に適合するように値で渡されますva_start

注:「安全」および「高速」バージョンは、一部のシステムでは機能しません。したがって、両方がまだリストされています。また、「高速」は、事前割り当てステップが正しいかどうかに完全に依存strcpyしています。それ以外の場合は、レンダリングが遅くなります。


3
スロー。なぜサイズを1増やすのですか?そして、この関数はいつ-1を返しますか?
0xDEAD BEEF

27
str.c_str()を上書きしていますか?危ないですか?
クォンタム

8
参照引数を指定したva_startは、MSVCで問題があります。サイレントに失敗し、ランダムメモリへのポインタを返します。回避策として、std :: string&fmtの代わりにstd :: string fmtを使用するか、ラッパーオブジェクトを記述します。
スティーブハノフ

6
私が+1したのは、これがおそらくstd :: stringsの実装方法に基づいて機能することを知っているからです。ただし、c_strは、基になる文字列を変更するための場所ではありません。読み取り専用であることになっています。
Doug T.

6
そして、結果の文字列長を事前に取得するには、次を参照してください。stackoverflow.com / a / 7825892/908336sizeの最初の呼び出しで取得できる場合、各反復 で増加するポイントがわかりませんvsnprintf()
Massood Khaari 2013年

107

boost::format() 必要な機能を提供します。

Boostフォーマットライブラリの概要から:

フォーマットオブジェクトはフォーマット文字列から作成され、operator%を繰り返し呼び出すことで引数が与えられます。次に、これらの引数のそれぞれが文字列に変換され、format-stringに従って、1つの文字列に結合されます。

#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、2010

7
ブーストフォーマットは大きいだけでなく、非常に遅いです。zverovich.net/2013/09/07/…およびboost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…を
vitaut

14
プロジェクトのどこかにboostを含めると、コンパイル時間が大幅に増加します。大規模なプロジェクトの場合、それはおそらく問題ではありません。小さなプロジェクトの場合、ブーストは厄介です。
quant_dev 2015

2
@vitaut 代替案と比較すると、それはひどくリソースを消費していますが。どのくらいの頻度で文字列をフォーマットしますか?数マイクロ秒しかかからず、ほとんどのプロジェクトで数十回しか使用しないことを考えると、文字列のフォーマットに重点を置いていないプロジェクトでは目立ちませんよね?
AturSams 2015

2
残念ながら、boost :: formatは同じように機能しません。var_argsを受け入れません。一部の人々は、単一のプログラムに関連するすべてのコードを同じように見たり、同じイディオムを使用したりしたいと考えています。
-xor007

88

C ++ 20には、APIの点でstd::format類似sprintfしていますが、完全にタイプセーフであり、ユーザー定義タイプで機能し、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、または他の回答のほとんどここでは、残念ながら彼らは可変引数を使用して、GCCのようなものを使用しない限り、本質的に安全ではないformat唯一のリテラル形式の文字列を使用しています属性を。これらの関数が安全でない理由は、次の例で確認できます。

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

string_formatErik Aronestyの回答からの実装はどこにありますか。このコードはコンパイルされますが、実行しようとするとクラッシュする可能性があります。

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

免責事項:私は{fmt}とC ++ 20の作成者std::formatです。


私見あなたが含ま欠場 error: 'fmt' has not been declared
セルジオ

これは単なるスニペットであり、完全なコードではありません。明らかに、<fmt / format.h>をインクルードし、コードを関数に配置する必要があります。
vitaut

私のために、あなたはスニペットでそれを含める必要があり、フィードバックに感謝私見それほど明白ではありません
セルジオ

1
fmt実装などはC ++ 20に追加されました!stackoverflow.com/a/57286312/895245 fmtは現在、そのサポートを主張しています。お見事!
Ciro Santilli郝海东冠状病六四事件法轮功

2
@vitautこれであなたの仕事をありがとう!
Curt Nichols


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 2013

1
stackoverflow.com/a/7825892/908336のコードを使用して、結果の文字列長を事前に取得します。そして、あなたは、例外セーフなコードのためのスマートポインタを使用することができますstd::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

これがフォールバックの場合に正しいかどうかはわかりません。引数を正しく表示するには、2番目のvsnprintf()でvlのva_copyを実行する必要があると思います。例については、github.com
Josh Haberman

15

std::string「sprintf」方式でフォーマットするには、snprintf(引数nullptr0)を呼び出して、必要なバッファーの長さを取得します。次のような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;
}

GCCなどでC ++ 11サポートを使用してコンパイルします。 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 2016年

using char[]char*with new の違いは、前者の場合、bufがスタックに割り当てられることです。小さなバッファでも問題ありませんが、結果の文字列のサイズを保証できないため、を使用する方が少し良いnewです。たとえば、私のマシンではstring_sprintf("value: %020000000d",5)、スタック上で配列を使用するとコアダンプが5の前にとんでもない数の先行ゼロが出力されますが、動的に割り当てられた配列を使用すると問題なく動作しますnew char[length + 1]
user2622016

フォーマットされた出力に必要な実際のバフサイズを取得するための非常に賢いアイデア
Chris

1
@ user2622016:解決策をありがとう!std::move 余計ですのでご了承ください。
Mihai 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)-functionは、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 2013年

私は彼のコードの一部がどのように機能するかを誤解し、それが多くのサイズ変更に影響していると考えました。再調査は私が間違っていたことを示しています。あなたのコードは正しいです。
Mooing Duck 2013

Erik Aronestyの答えを基に構築されたのは、赤いニシンです。彼の最初のコードサンプルは安全ではなく、2番目のコードサンプルは非効率的で不器用です。クリーンな実装は、vprintfファミリーの関数のいずれかのbuf_sizがゼロの場合、何も書き込まれず、バッファーがnullポインターになる可能性があるという事実によって明確に示されますが、戻り値(書き込まれるバイト数には含まれません) nullターミネータ)は引き続き計算され、返されます。生産品質の答えはここにある:stackoverflow.com/questions/2342162/...
ダグラスDaseeco

10

これは、Googleが行う方法StringPrintf(BSDライセンス)
とFacebookが非常に似た方法で行う方法StringPrintf(Apacheライセンス)です。
どちらも便利StringAppendFです。


10

この非常に人気のある質問に対する私の2セント。

-like functionsマンページprintfを引用するには:

正常に戻ると、これらの関数は出力された文字数を返します(文字列への出力を終了するために使用される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が必要なため、の戻り値にはA が必要です。直感的には、ほとんどのアーキテクチャーで欠落していると、整数が部分的にで上書きされます。これは、の実際のパラメータとしての評価に発生するため、効果は表示されません。std::snprintf\0+1required0requiredstd::snprintf

ただし、この問題は、たとえばコンパイラの最適化によって変化する可能性がありrequiredます。コンパイラが変数にレジスタを使用することを決定した場合はどうなりますか?これは、セキュリティの問題を引き起こす可能性のある種類のエラーです。


1
snprintfは常に終端のnullバイトを追加しますが、それがない場合の文字数を返します。このコードは常に最後の文字をスキップしないのですか?
alexk7

@ alexk7、ナイスキャッチ!答えを更新しています。コードは最後の文字をスキップしませんが、bytesバッファの終わりを超えて、おそらくrequired整数を超えて書き込みます(幸い、その時点で既に評価されています)。
Dacav 2015年

1
ほんの少しのヒント:バッファーサイズが0の場合、a nullptrをバッファー引数として渡してchar b;、コードの行を削除できます。(出典
iFreilicht 2015年

@iFreilicht、修正しました。また、+ 1
Dacav 2015年

2
「char bytes [required]」を使用すると、ヒープではなくスタックに割り当てられます。これは、大きな形式の文字列では危険な場合があります。代わりにnewを使用することを検討してください。ヤン
ヤンヌス

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

予測可能な線形効率

質問の仕様ごとに、安全で信頼性が高く、予測可能な再利用可能な機能を実現するには、2つのパスが必要です。再利用可能な関数でのvargsのサイズの分布についての推定は悪いプログラミングスタイルであり、回避する必要があります。この場合、vargsの任意の大きな可変長表現は、アルゴリズムを選択する際の重要な要素です。

オーバーフロー時の再試行は指数関数的に非効率的です。これは、C ++ 11標準委員会が書き込みバッファーがnullのときに予行演習を提供する上記の提案を議論したときに議論された別の理由です。

上記の本番環境対応の実装では、最初の実行は、割り当てサイズを決定するためのそのようなドライランです。割り当ては行われません。printfディレクティブの解析とvargsの読み取りは、何十年にもわたって非常に効率的になっています。些細な場合のわずかな非効率性を犠牲にする必要がある場合でも、再利用可能なコードは予測可能でなければなりません。

セキュリティと信頼性

Andrew Koenigは、ケンブリッジのイベントでの講演後、少人数のグループに対して、「ユーザー機能は、例外的な機能の失敗の悪用に依存すべきではない」と述べました。いつものように、彼の知恵はそれ以来記録に真実を示してきました。多くの場合、修正およびクローズされたセキュリティバグの問題は、修正前に悪用されたホールの説明に再試行ハックを示しています。

これは、sprintfの代替案C9X改訂提案、ISO IECドキュメントWG14 N6​​45 / X3J11 96-008のnullバッファー機能の正式な標準改訂提案で言及されています。動的メモリの可用性の制約内で、印刷ディレクティブごとに挿入された任意の長い文字列「%s」は例外ではなく、「例外的な機能」を生成するために悪用することはできません。

この回答の最初の段落にリンクされているC ++ Reference.orgページの下部にあるサンプルコードと一緒に提案を検討してください。

また、失敗例のテストが成功例のように堅牢であることはめったにありません。

携帯性

すべての主要なOSベンダーは、c ++ 11標準の一部としてstd :: vsnprintfを完全にサポートするコンパイラを提供しています。ディストリビューションを維持しなくなったベンダーの製品を実行しているホストには、多くの理由でg ++またはclang ++を提供する必要があります。

スタックの使用

std :: vsnprintfへの最初の呼び出しでのスタックの使用は、2番目の呼び出しと同じかそれ以下になり、2番目の呼び出しが始まる前に解放されます。最初の呼び出しがスタックの可用性を超えると、std :: fprintfも失敗します。


簡潔で堅牢。これは、vsnprintf-sに準拠していないHP-UX、IRIX、Tru64では失敗する可能性があります。編集:また、2パスがパフォーマンスにどのように影響するかを考慮します。最も一般的な小さな文字列フォーマットの場合、最初のパスの推測は十分に大きいと思われますか?
エンジニア

FWIW、私が参照していた推測では、最初の実行が発生する場所にスタック割り当てバッファを使用しています。適合する場合、2回目の実行とそこで発生する動的割り当てのコストを節約できます。おそらく、小さな文字列は大きな文字列よりも頻繁に使用されます。私の大雑把なベンチマークでは、その戦略は(ほとんど)小さな文字列の実行時間を半分にし、上記の戦略の数パーセント(固定オーバーヘッドかもしれません)以内です。予行演習などを採用したC ++ 11の設計について詳しく教えてください。それについて読みたいのですが。
エンジニア、

@Engineerist、あなたの質問は、コードの上下にある回答の本文で対処されています。そのようにサブトピックを読みやすくすることができます。
ダグラスダシーコ

6

C ++ 20 std::format

入荷しました!この機能については、http//www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.htmlで説明されており、Pythonに似た.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ライブラリは、ポリフィルが必要な場合にそれを実装すると主張しています:https : //github.com/fmtlib/fmt

C ++ 20の実装std::format

以前に言及されました:std :: string 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の答えを基に構築されたのは、赤いニシンです。彼の最初のコードサンプルは安全ではなく、2番目のコードサンプルはループが効率的でなく、不格好です。クリーンな実装は、vprintfファミリーの関数のいずれかのbuf_sizがゼロの場合、何も書き込まれず、バッファーがnullポインターになる可能性があるという事実によって明確に示されますが、戻り値(書き込まれるバイト数には含まれません) nullターミネータ)は引き続き計算され、返されます。生産品質の答えはここにある:stackoverflow.com/questions/2342162/...
ダグラスDaseeco

Erik Aronestyの回答は、私が追加されてから編集されています。構築された文字列を格納するためにvector <char>を使用するオプションを強調したかったのです。C ++コードからC関数を呼び出すときに、この手法をよく使用します。質問に34の回答があることは興味深いです。
ChetS 2018年

vfprintfページのcppreference.comの例は後で追加されました。最善の答えは現在受け入れられている答えだと思います。printfバリアントの代わりに文字列ストリームを使用するのがC ++のやり方です。しかし、私の答えは提供されたときに付加価値を与えました。当時は他の回答よりも段階的に優れていました。現在、標準にはstring_view、パラメーターパック、およびVariadicテンプレートがあり、新しい回答にこれらの機能を含めることができます。私の回答については、追加の投票には値しないかもしれませんが、削除または投票に値するわけではないので、そのままにしておきます。
ChetS 2018年

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 2014年

3

stringには必要なものがありませんが、std :: stringstreamにはあります。文字列ストリームを使用して文字列を作成し、文字列を抽出します。ここにあなたができることの包括的なリストがあります。例えば:

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

doubleまたはfloatを印刷すると、小数点以下10桁の精度が得られます。


8
それでも、printfが提供するコントロールの近くには何も表示されませんが、それはすばらしいことです。
Erik Aronesty 2014

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、それは引数の型をチェックし、-Wallとまともな警告を与えるためにはgccを伝えるよう、:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
アーロンMcDaid

2
への呼び出しを追加しましたva_end「va_startまたはva_copyを呼び出す関数が戻る前にva_endが呼び出されない場合、動作は未定義です。」 - docs
Aaron McDaid

1
失敗時にはポインタ値が未定義であるため、vasprintfの戻り結果を確認する必要があります。したがって、おそらく<new>を含めて追加します。if(size == -1){throw std :: bad_alloc(); }
Neil McGill

良い点、私はそれに応じて答えを変更しました、throw std::bad_alloc();私はコードベースでC ++例外を使用していないので、コメントをそこに置く代わりにそこに置くことにしました、そしてそうする人々のために、彼らは簡単にそれをベースに追加できますソースコメントとあなたのコメントはここに。
Thomas 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の初期化では、バッファ全体がすでにゼロに設定されています。memsetする必要はありません...
EricSchaefer

これはデータの完全な追加コピーvsnprintfを行います。文字列に直接使用することが可能です。
Mooing Duck 2013

2

Dacavpixelpointの答えからアイデアを取り入れました。私は少し遊んでこれを得ました:

#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()、初期バッファがすでに十分である場合の2回目の呼び出しを防ぐために初期バッファを使用する別のバージョンがあります。

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

(このバージョンはPiti Ongmongkolkulの回答とまったく同じnewdelete[]、andを使用しないこと、および作成時にサイズを指定することがわかりましたstd::string

ここで使用newしていない考え方delete[]は、割り当ておよび割り当て解除関数を呼び出す必要がないため、ヒープ上でのスタックの使用を暗示することですが、適切に使用しないと、一部の(おそらく古い、またはおそらく脆弱なシステム)。これが問題になる場合は、代わりにnewおよびを使用することを強くお勧めしdelete[]ます。ここでの唯一の問題はvsnprintf()、制限付きで既に呼び出されている割り当てに関することです。そのため、2番目のバッファーに割り当てられたサイズに基づいて制限を指定すると、それらも防止されます。)


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では、バッファーの値は未定義になります。必要性:if(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

iomanipヘッダーファイルを使用して、C ++出力をcoutでフォーマットできます。setprecision、setfillなどのヘルパー関数を使用する前に、必ずiomanipヘッダーファイルをインクルードしてください。

以下は、私が「累積」したベクトルの平均待機時間を出力するために過去に使用したコードスニペットです。

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

String.h:

#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文字の余地があると想定しても安全ですか?size + 1文字を割り当てない実装はありますか?それを行う方が安全でしょうdst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

どうやら私の前のコメントへの答えは次のとおりです。null文字があると想定するのは安全ではありません。特にC ++ 98仕様に関して:「data()+ size()で値にアクセスすると、未定義の動作が発生します。この関数によって返される値が指す文字シーケンスがnull文字で終了するという保証はありません。stringを参照してくださいそのような保証を提供する関数の場合は:: c_str。 プログラムはこのシーケンスの文字を変更してはなりません。 "ただし、C ++ 11仕様では、datac_strは同義語です。
drwatsoncode 2016


1

非常にシンプルなソリューション。

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

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

アップデート1:追加されたfmt::formatテスト

ここで紹介した方法に関する独自の調査を行った結果、ここで言及したものとは正反対の結果が得られました。

私は4つの方法で4つの関数を使用しました:

  • 可変関数+ vsnprintf+std::unique_ptr
  • 可変関数+ vsnprintf+std::string
  • 可変テンプレート関数+ std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfmtライブラリの関数

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実装は、ここから取得されます:タプルを超える反復

#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 3、で実行されWindows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GBます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.