std::string を sprintf のようにフォーマットする 質問する

std::string を sprintf のようにフォーマットする 質問する

フォーマットする必要がありますstd::stringsprintfそれをファイル ストリームに送信します。どうすればこれができるでしょうか?

ベストアンサー1

最新の C++ ではこれが非常に簡単になります。

C++20

C++20紹介するstd::formatは、まさにそれを可能にします。これは、次のような置換フィールドを使用します。Pythonのもの:

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

コード元翻訳元、CC BY-SA および GFDL

チェックしてくださいコンパイラサポートページ標準ライブラリ実装で利用できるかどうかを確認します。

2023-07-18 現在、部分的なサポートは以下からご利用いただけます:

それ以外の場合は、以下のC++11ソリューションを使用するか、図書館{fmt}は と同じ意味を持ちますstd::format


C++11

C++11sstd::snprintf、これはすでにかなり簡単で安全な作業になりました。

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

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    auto size = static_cast<size_t>( size_s );
    std::unique_ptr<char[]> buf( new char[ size ] );
    std::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*を使用してに書き込みstd::snprintf、それを に変換しますstd::string

まず、特別な条件を使用してchar配列の必要な長さを決定しますsnprintf翻訳元:

戻り値

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

これは、必要なサイズが文字数に 1 を加えた数であることを意味します。これにより、ヌル終端文字が他のすべての文字の後に配置され、文字列コンストラクターによって再び切り捨てられるようになります。この問題は、コメントで @alexk7 によって説明されました。

int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintfエラーが発生した場合は負の数を返すので、フォーマットが期待どおりに機能したかどうかを確認します。これを行わないと、コメントで @ead が指摘しているように、サイレント エラーが発生したり、巨大なバッファーが割り当てられたりする可能性があります。

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

size_sが負になることはないことがわかっているので、静的キャストを使用して signed からintunsigned に変換しますsize_t。この方法であれば、最も細かいことにこだわるコンパイラでも、次の行で発生する変換について文句を言うことはありません。

size_t size = static_cast<size_t>( size_s );

次に、新しい文字配列を割り当てて に割り当てますstd::unique_ptr。手動で再度削除する必要がなくなるため、通常はこれが推奨されます。

unique_ptrコンストラクターが例外をスローするとメモリの割り当てを解除できないため、これはユーザー定義型で を割り当てる安全な方法ではないことに注意してください。

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

C++14代わりに、make_uniqueこれはユーザー定義型に対して安全です。

auto buf = std::make_unique<char[]>( size );

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

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

std::string最後に、末尾の null 終端文字を省略して、そこから新しいものを作成して返します。

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

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


std::string引数リストでも使用したい場合は、この要点


追加情報ビジュアルスタジオユーザー:

で説明したようにこの答えマイクロソフトは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つのソリューション役に立つかもしれません。固定バッファ長のソリューションは使用しないでください。トラブルを招くだけです。

おすすめ記事