web-dev-qa-db-ja.com

C ++:fprintfの結果をsprintfなしのstd :: stringとして取得する方法

私はC++で実装されているオープンソースのUNIXツールを使用していますが、コードを変更して、必要な処理を実行する必要があります。パッチがアップストリームで受け入れられることを期待して、可能な限り最小限の変更を加えたいと思います。標準のC++で実装可能で、外部依存関係をこれ以上作成しないソリューションが推奨されます。

これが私の問題です。私はC++クラスを持っています-それを「A」と呼びましょう-現在fprintf()を使用してその高度にフォーマットされたデータ構造をファイルポインタに出力します。そのprint関数では、複数のメンバークラスの同じように定義されたprint関数も再帰的に呼び出します(「B」は例です)。 Aのインスタンスのprint()結果に設定する必要があるメンバーstd :: string "foo"を持つ別のクラスCがあります。これをAのto_str()メンバー関数と考えてください。

擬似コードの場合:

class A {
public:
  ...

  void print(FILE* f);
  B b;

  ...  
};

...

void A::print(FILE *f)
{
  std::string s = "stuff";
  fprintf(f, "some %s", s);
  b.print(f);
}

class C {
  ...
  std::string foo;
  bool set_foo(std::str);
  ...
}

...

A a = new A();
C c = new C();

...

// wish i knew how to write A's to_str()
c.set_foo(a.to_str());

Cはかなり安定していますが、AとB(およびAの残りの依存関係)は流動的な状態にあるため、必要なコード変更が少ないほど良いことを述べておきます。現在のprint(FILE * F)インターフェースも保持する必要があります。 A :: to_str()を実装するためのいくつかのアプローチを検討しましたが、それぞれに長所と短所があります。

  1. Fprintf()の呼び出しをsprintf()に変更します

    • フォーマット文字列を書き直す必要はありません
    • print()は次のように再実装できます:fprint(f、this.to_str());
    • しかし、手動でchar []を割り当て、多くのc文字列をマージし、最後に文字配列をstd :: stringに変換する必要があります。
  2. 文字列ストリームでa.print()の結果をキャッチしてみてください

    • すべてのフォーマット文字列を<<出力フォーマットに変換する必要があります。変換するfprintf()は何百もあります:-{
    • uNIXファイルハンドルから出力ストリームを作成するための標準的な方法がないため、print()を書き直す必要があります(ただし、 この人は可能かもしれないと言っています )。
  3. Boostの文字列を使用する フォーマットライブラリ

    • より多くの外部依存関係。うん。
    • フォーマットの構文はprintf()とは十分に異なるため、煩わしいものです。

    printf(format_str、args)-> cout << boost :: format(format_str)%arg1%arg2%など

  4. Qtを使用する QString :: asprintf()

    • 別の外部依存関係。

それで、私はすべての可能なオプションを使い果たしましたか?もしそうなら、あなたは私の最善の策はどれだと思いますか?そうでない場合、私は何を見落としていますか?

ありがとう。

18
underspecified

私は#3:ブースト文字列フォーマットライブラリを使用していますが、フォーマット仕様の違いに問題があったことは一度もないことを認めなければなりません。

私にとっては魅力のように機能します-そして外部依存関係はもっと悪くなる可能性があります(非常に安定したライブラリ)

編集:printfの代わりにboost :: formatを使用する方法の例を追加:

sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42);

boost :: formatライブラリを使用すると次のようになります:

string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);

これがboost :: formatの使用法を明確にするのに役立つことを願っています

私は4つまたは5つのアプリケーション(フォーマットされた文字列をファイルに書き込む、またはカスタム出力をログファイルに書き込む)でsprintf/printfの代わりにboost :: formatを使用しましたが、フォーマットの違いに関する問題はありませんでした。異なる(多かれ少なかれあいまいな)フォーマット指定子があるかもしれませんが、私は問題を抱えたことはありませんでした。

対照的に、私はストリームでは実際にはできないフォーマット仕様をいくつか持っていました(私が覚えている限り)

13
bernhardrusch

これは、機能を「sprintf」と同じにするために私が好きなイディオムですが、std :: stringを返し、バッファオーバーフローの問題の影響を受けません。このコードは私が書いているオープンソースプロジェクト(BSDライセンス)の一部なので、誰もが自由にこれを使用できます。

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

std::string
format (const char *fmt, ...)
{
    va_list ap;
    va_start (ap, fmt);
    std::string buf = vformat (fmt, ap);
    va_end (ap);
    return buf;
}



std::string
vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.
    size_t size = 1024;
    char buf[size];

    // Try to vsnprintf into our buffer.
    va_list apcopy;
    va_copy (apcopy, ap);
    int needed = vsnprintf (&buf[0], size, fmt, ap);
    // NB. On Windows, vsnprintf returns -1 if the string didn't fit the
    // buffer.  On Linux & OSX, it returns the length it would have needed.

    if (needed <= size && needed >= 0) {
        // It fit fine the first time, we're done.
        return std::string (&buf[0]);
    } else {
        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So do a malloc of the right size and try again.
        // This doesn't happen very often if we chose our initial size
        // well.
        std::vector <char> buf;
        size = needed;
        buf.resize (size);
        needed = vsnprintf (&buf[0], size, fmt, apcopy);
        return std::string (&buf[0]);
    }
}

編集:私がこのコードを書いたとき、これにはC99準拠が必要であり、Windows(および古いglibc)のvsnprintfの動作が異なり、失敗した場合はスペースの量の決定的な尺度ではなく-1が返されることを知りませんでした。が必要です。これが私の改訂されたコードです、誰もがそれを見ることができます、そしてあなたがそれが大丈夫だと思うなら、私はそれをリストされた唯一のコストにするためにもう一度編集します:

std::string
Strutil::vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.  Be prepared to allocate dynamically if it doesn't fit.
    size_t size = 1024;
    char stackbuf[1024];
    std::vector<char> dynamicbuf;
    char *buf = &stackbuf[0];
    va_list ap_copy;

    while (1) {
        // Try to vsnprintf into our buffer.
        va_copy(ap_copy, ap);
        int needed = vsnprintf (buf, size, fmt, ap);
        va_end(ap_copy);

        // NB. C99 (which modern Linux and OS X follow) says vsnprintf
        // failure returns the length it would have needed.  But older
        // glibc and current Windows return -1 for failure, i.e., not
        // telling us how much was needed.

        if (needed <= (int)size && needed >= 0) {
            // It fit fine so we're done.
            return std::string (buf, (size_t) needed);
        }

        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So try again using a dynamic buffer.  This
        // doesn't happen very often if we chose our initial size well.
        size = (needed > 0) ? (needed+1) : (size*2);
        dynamicbuf.resize (size);
        buf = &dynamicbuf[0];
    }
}
39
Larry Gritz

次の解決策が考えられます。

void A::printto(ostream outputstream) {
    char buffer[100];
    string s = "stuff";
    sprintf(buffer, "some %s", s);
    outputstream << buffer << endl;
    b.printto(outputstream);
}

B::printto同様)、および定義

void A::print(FILE *f) {
    printto(ofstream(f));
}

string A::to_str() {
    ostringstream os;
    printto(os);
    return os.str();
}

もちろん、バッファオーバーフローを回避するには、sprintfではなくsnprintfを実際に使用する必要があります。よりリスクの高いsprintfsを<<形式に選択的に変更して、より安全でありながら、変更をできるだけ少なくすることもできます。

1
Jan de Vos

LokiライブラリのSafeFormatヘッダーファイル( http://loki-lib.sourceforge.net/index.php?n=Idioms.Printf )を試してみてください。これはboostの文字列フォーマットライブラリに似ていますが、printf(...)関数の構文を保持します。

これがお役に立てば幸いです。

1
Kevin

Std :: stringとiostreamsを、setw()呼び出しなどのフォーマットで使用できます。

1
Yann Ramin

{fmt}ライブラリ は、printf互換のフォーマット( POSIX仕様 に従った位置引数を含む)を実行して結果を返すfmt::sprintf関数を提供しますas std::string

std::string s = fmt::sprintf("The answer is %d.", 42);

免責事項:私はこのライブラリの作者です。

0
vitaut

これはシリアル化についてですか?または適切に印刷しますか?前者の場合は、boost :: serializationも検討してください。それはすべて、オブジェクトとサブオブジェクトの「再帰的」シリアル化に関するものです。

0
Assaf Lavie