3

私は C++ に比較的慣れていないので、まだ C++ 標準ライブラリを理解しています。std::stringC からの移行を支援するために、 printf スタイルのフォーマッターを使用してa をフォーマットしたいと考えています。私stringstreamはよりタイプセーフなアプローチであることを理解していますが、printfスタイルの方が読みやすく、扱いやすいと感じています(少なくとも当分の間)。これは私の機能です:


using namespace std;

string formatStdString(const string &format, ...)
{
    va_list va;
    string output;
    size_t needed;
    size_t used;

    va_start(va, format);
    needed = vsnprintf(&output[0], 0, format.c_str(), va);
    output.resize(needed + 1); // for null terminator??
    va_end(va);    

    va_start(va, format);
    used = vsnprintf(&output[0], output.capacity(), format.c_str(), va);
    // assert(used == needed);
    va_end(va);

    return output;
}

これはうまくいきます。私がよくわからないいくつかのことは次のとおりです。

  1. null ターミネータ用のスペースを確保する必要がありますか、それとも不要ですか?
  2. capacity()ここで呼び出す関数は正しいですか? 文字length()列の最初の文字が'\0'.

この文字列の内容をソケットに書き込んでいるときに (および を使用しc_str()length())、受信側で null バイトがポップアップすることがあります。この関数をまったく使用しない場合、null バイトは表示されません。

4

7 に答える 7

13

現在の標準 (今後の標準はここで異なります) では、 によって管理される内部メモリ バッファーstd::stringが連続しているという保証や、.c_str()メソッドが内部データ表現へのポインターを返すという保証はありません (実装は連続した読み取りを生成することが許可されています)。実際の内部データへのポインタは.data()メンバー メソッドで取得できますが、定数ポインタも返すことに注意してください: つまり、内容を変更するためのものではありません。それによるバッファの戻り.data()は、必ずしも null で終了するとは限りません。実装は、 が呼び出されたときに null 終了を保証する必要があるだけですc_str().data().c_str()が呼び出された場合、実装は\0、後者が呼び出されたときに をバッファーの最後に追加できます。

標準はロープの実装を許可することを目的としているため、原則として、試みていることを行うのは安全ではありません。標準の観点からは、中間std::vector(保証された連続性、および&myvector[0]へのポインタである保証があります) を使用する必要があります。実バッファの最初に割り当てられたブロック)。

私が知っているすべての実装では、によって処理される内部メモリstd::stringは実際には連続したバッファであり、使用.data()は未定義の動作 (定数変数への書き込み) ですが、正しくない場合でも機能する可能性があります (回避します)。など、この目的のために設計された他のライブラリを使用する必要がありますboost::format

ヌル終端について。最終的に未定義のパスをたどることに決めた場合...ライブラリがヌルターミネータをバッファに書き込むため、ヌルターミネータに余分なスペースを割り当てる必要があります。ここでの問題は、C スタイルの文字列とは異なり、std::strings は内部的に null ポインターを保持できるため、文字列のサイズを変更して、 no を含む先頭から最大の連続したメモリ ブロックに収まるようにする必要があることです\0。それはおそらく、偽のヌル文字で見つかった問題です。これは、使用vsnprintf(またはファミリ) の悪いアプローチに続いてstr.resize( strlen( str.c_str() ) )、最初の\0.

全体として、私はこのアプローチに反対し、C++ の書式設定方法に慣れるか、サード パーティ ライブラリ (boost はサード パーティですが、最も標準的な非標準ライブラリでもあります) を使用するか、ベクトルを使用するか、メモリを管理することを主張します。 Cのように...しかし、最後のオプションはペストのように避けるべきです。

// A safe way in C++ of using vsnprintf:
std::vector<char> tmp( 1000 ); // expected maximum size
vsnprintf( &tmp[0], tmp.size(), "Hi %s", name.c_str() ); // assuming name to be a string
std::string salute( &tmp[0] );
于 2010-05-21T07:59:58.353 に答える
5

ストリームboost::formatよりも優先する場合は、を使用します。printf()

編集:これを明確にするために、実際には、ストリームを使用する必要があると述べた Alan に完全に同意します。

于 2010-05-21T07:47:32.880 に答える
2

&output[0] で参照される文字列のレイアウトが連続していて、書き込めるという保証はないと思います。

C++03 以降、連続したストレージを持つことが保証されているバッファーとして代わりに std::vector を使用してください。

using namespace std;

string formatStdString(const string &format, ...)
{
    va_list va;
    vector<string::value_type> output(1); // ensure some storage is allocated
    size_t needed;
    size_t used;

    va_start(va, format);
    needed = vsnprintf(&output[0], 0, format.c_str(), va);
    output.resize(needed); // don't need null terminator
    va_end(va);    

    // Here we should ensure that needed != 0
    va_start(va, format);
    used = vsnprintf(&output[0], output.size(), format.c_str(), va); // use size()
    // assert(used == needed);
    va_end(va);

    return string(output.begin(), output.end());
}

注: ベクターに初期サイズを設定する必要があります。そうしないと、ステートメント &output[0] が存在しないアイテムを参照しようとする可能性があるためです (内部バッファーがまだ割り当てられていない可能性があるため)。

于 2010-05-21T08:01:50.753 に答える
0

関数の可変引数リストの私の実装は次のようになります。

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

  string retStr("");

  if (NULL != fmt)
  {
     va_list marker = NULL;

     // initialize variable arguments
     va_start(marker, fmt);

     // Get formatted string length adding one for NULL
     size_t len = _vscprintf(fmt, marker) + 1;

     // Create a char vector to hold the formatted string.
     vector<char> buffer(len, '\0');
     int nWritten = _vsnprintf_s(&buffer[0], buffer.size(), len, fmt,
marker);

     if (nWritten > 0)
     {
        retStr = &buffer[0];
     }

     // Reset variable arguments
     va_end(marker);
  }

  return retStr;
}
于 2010-05-21T08:17:31.677 に答える
0

std::string クラスが null ターミネータを処理します。

ただし、指摘したように、生の基になる文字列バッファーに vnsprintf を使用しているため (C アナクロニズムは難しい...)、null ターミネーター用のスペースがあることを確認する必要があります。

于 2010-05-21T07:31:11.717 に答える
0

1) null ターミネータ用のスペースを作成する必要はありません。
2) capacity() は、文字列が内部的に予約したスペースの量を示します。length() は、文字列の長さを示します。おそらく capacity() は必要ありません

于 2010-05-21T07:32:55.270 に答える
0

C からの移行を支援するために、printf スタイルのフォーマッターを使用して std::string をフォーマットしたいと考えています。

ただしないでください:(

これを行うと、実際に C++ を学習するのではなく、C++ コンパイラで C をコーディングすることになります。それは悪い考え方であり、悪い習慣であり、std::o*stream回避するためにクラスが作成された問題を広めます。

stringstream の方がタイプ セーフなアプローチであることは理解していますが、printf スタイルの方が読みやすく、扱いやすい (少なくとも当面は) と感じています。

これは、よりタイプセーフなアプローチではありません。これはタイプセーフなアプローチです。それ以上に、依存関係を最小限に抑え、追跡しなければならない問題の数を減らし (明示的なバッファー割り当てやヌル文字ターミネーターの追跡など)、コードの保守を容易にします。

その上で、完全に拡張可能/カスタマイズ可能です:

  • ロケールのフォーマットを拡張できます

  • カスタムデータ型の入出力操作を定義できます

  • 新しいタイプの出力フォーマットを追加できます

  • 新しいバッファ I/O タイプを追加できます (たとえば、std::clog をウィンドウに書き込みます)。

  • さまざまなエラー処理ポリシーを組み込むことができます。

std::o*streamクラスのファミリーは非常に強力であり、一度正しく使用することを学ぶと、元に戻らないことは間違いありません。

非常に具体的な要件がない限り、C++ で printf を作成するよりも、o*stream クラスの学習に時間を費やすほうがよいでしょう。

于 2010-05-21T08:32:16.490 に答える