18

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

これが私の問題です。私はC++クラスを持っています-それを「A」と呼びましょう-現在、fprintf()を使用して、その非常にフォーマットされたデータ構造をファイルポインターに出力します。その印刷関数では、複数のメンバー クラス ("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 の文字列フォーマット ライブラリを使用する

    • より多くの外部依存関係。うん。
    • Format の構文は printf() とはかなり異なり、面倒です。

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

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

    • 別の外部依存関係。

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

ありがとう。

4

7 に答える 7

41

これは、「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];
    }
}
于 2008-09-16T06:52:13.540 に答える
13

私は#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を使用しましたが、フォーマットの違いに関する問題はありませんでした。いくつかの(多かれ少なかれあいまいな)フォーマット指定子が異なっているかもしれません-しかし、私は問題を抱えたことはありませんでした。

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

于 2008-09-16T06:18:25.510 に答える
2

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

于 2008-09-16T06:19:00.377 に答える
1

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

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

于 2008-09-16T08:00:22.113 に答える
1

以下は代替ソリューションの可能性があります。

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 を << 形式に選択的に変更して、安全性を高めながら変更をできるだけ少なくすることもできます。

于 2008-09-16T06:53:09.513 に答える
0

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

于 2008-09-16T06:18:19.940 に答える