development

sprintf와 같은 std :: string 형식

big-blog 2020. 2. 21. 22:27
반응형

sprintf와 같은 std :: string 형식


형식 std::stringsprintf지정하여 파일 스트림으로 보내야합니다. 어떻게해야합니까?


기본 버퍼에 대한 쓰기 액세스 권한이 없으므로 직접 수행 할 수 없습니다 (C ++ 11까지, Dietrich Epp의 설명 참조 ). 먼저 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();

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.

참고 : "safer"및 "faster"버전은 일부 시스템에서 작동하지 않습니다. 따라서 둘 다 여전히 나열됩니다. 또한 "빠른"은 사전 할당 단계의 정확성에 전적으로 달려 있으며, 그렇지 않으면 strcpy렌더링 속도가 느려집니다.


이용 C ++ (11) std::snprintf 이 꽤 쉽고 안전하게 작업이된다. 고정 된 버퍼 길이와 변수를 사용하는 C ++ 11 이전에 작성된이 질문에 대한 많은 답변을 보았습니다. 안전, 효율성 및 선명도 이유로 권장하지 않습니다.

#include <memory>
#include <iostream>
#include <string>
#include <cstdio>

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'
    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에 따라 라이센스가 부여되었습니다 .

라인 별 설명 :

목표 : A와 쓰기char*사용하여std::snprintfA와 그 변환 다음과std::string.

먼저, 원하는 char 배열 길이를 결정합니다.

에서 cppreference.com :

반환 값

[...] buf_size 제한으로 인해 결과 문자열이 잘리는 경우 함수는 제한이 적용되지 않은 경우 쓰여질 총 문자 수 (종료 null 바이트 제외)를 반환합니다.

이것은 원하는 크기가 문자 수 에 1을 더한 것을 의미 하므로 널 종료자는 다른 모든 문자 뒤에 앉아서 문자열 생성자에 의해 다시 잘릴 수 있습니다. 이 문제는 의견에서 @ alexk7에 의해 설명되었습니다.

그런 다음 새 문자 배열을 할당하고에 할당합니다 std::unique_ptr. 수동으로 delete다시 할 필요가 없으므로 일반적으로 권장 됩니다.

unique_ptr생성자가 예외를 발생시키는 경우 메모리 할당을 해제 할 수 없으므로 사용자 정의 형식 으로을 할당하는 안전한 방법은 아닙니다 !

그 후, 우리는 물론 snprintf의도 된 용도로 사용하고 형식화 된 문자열을 쓰고 char[]나중에 새 std::string것으로 작성하여 반환 할 수 있습니다.


여기 에 실제 예제가 있습니다 .


std::string인수 목록 에서도 사용하려면 이 요점을 살펴보십시오 .


Visual Studio 사용자를 위한 추가 정보 :

이 답변 에서 설명한대로 Microsoft는 이름 std::snprintf_snprintf(예,없이 std::) 로 바꿨 습니다 . MS는 더 이상 사용하지 않는 것으로 설정하고 _snprintf_s대신 사용하는 것이 좋습니다. 그러나 _snprintf_s버퍼가 포맷 된 출력보다 0보다 작거나 작을 수는 없으며 출력 길이가 발생하면 계산 길이를 계산하지 않습니다. 따라서 컴파일 중에 사용 중단 경고를 제거하기 위해 파일 맨 위에 다음을 사용하는 다음 행삽입 할 수 있습니다 _snprintf.

#pragma warning(disable : 4996)

boost::format() 원하는 기능을 제공합니다.

Boost 형식 라이브러리 개요에서와 같이 :

format 객체는 format-string으로 구성되며 operator %에 대한 반복 호출을 통해 인수가 제공됩니다. 그런 다음 각 인수는 문자열로 변환되어 format-string에 따라 하나의 문자열로 결합됩니다.

#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"

C ++ 20에는 API std::format와 유사 sprintf하지만 완전히 유형 안전하고 사용자 정의 유형과 작동하며 Python과 같은 형식 문자열 구문을 사용하는 것이 포함됩니다.

#include <format>

std::string result = std::format("The answer is {}.", 42);

여기의 대부분의 다른 답변에 관해서는 불행히도 varargs를 사용 format하며 리터럴 형식 문자열에서만 작동하는 GCC 속성 과 같은 것을 사용하지 않으면 본질적으로 안전하지 않습니다 . 다음 예제에서 이러한 기능이 안전하지 않은 이유를 확인할 수 있습니다.

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

(가변) 템플릿을 사용하여 금고를 구현하고 printf형식으로 확장 할 수 std::string있습니다. 이것은에서 수행 된 {FMT} 라이브러리std::format이는에 안전한 대안 제공 sprintf반환 std::string(또는 제공된 버퍼에 쓰는) :

#include <fmt/core.h>

std::string format_str = "The answer is {}.";
std::string result = fmt::format(format_str, 42);

{fmt}는 인수 유형을 추적하며 유형이 형식 스펙과 일치하지 않으면 분할 결함이없고 constexpr형식 문자열 검사가 사용되는 경우 예외 또는 컴파일 타임 오류 만 있습니다.

면책 조항 : 저는 {fmt} 및 C ++ 20의 저자입니다 std::format.


printf와 같은 구문 만 원한다면 (printf를 직접 호출하지 않고) Boost Format을 살펴보십시오 .


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

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

[편집 '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'-way를 좋아하는 사람들에게는 다음과 같은 기능을 사용하고 있습니다. 원하는 경우 스프린트 항목에 더 가깝게 % 옵션을 확장 할 수 있습니다. 거기에있는 것들은 현재 나의 필요에 충분합니다. sprintf와 동일하게 stringf () 및 stringfappend ()를 사용합니다. ...의 매개 변수는 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;
}

구글이하는 방법은 다음과 같습니다 : StringPrintf(BSD 라이센스)
, 페이스 북은 비슷한 방식으로 수행합니다 : StringPrintf(아파치 라이센스)
둘 다 편리한 기능을 제공합니다 StringAppendF.


이 매우 인기있는 질문에 대한 나의 두 센트.

-like 함수맨 페이지printf 를 인용하려면 :

성공적으로 리턴되면이 함수는 인쇄 된 문자 수를 리턴합니다 (널로 출력을 종료하는 데 사용되는 널 바이트 제외).

snprintf () 및 vsnprintf () 함수는 크기가 바이트를 초과하지 않습니다 (종료 널 바이트 ( '\ 0') 포함). 이 한계로 인해 출력이 잘린 경우 리턴 값은 충분한 공간이 사용 가능한 경우 최종 문자열에 기록 된 문자 수 (종료 널 바이트 제외)입니다. 따라서 크기 이상의 반환 값은 출력이 잘린 것을 의미합니다.

다시 말해, 제정신 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변수에 레지스터를 사용하기로 결정하면 어떻게됩니까? 이것은 때때로 보안 문제를 야기하는 일종의 오류입니다.


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 사용


테스트를 거친 생산 품질 답변

이 답변은 표준 준수 기술로 일반적인 경우를 처리합니다. 페이지 하단 근처의 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; }

예측 가능한 선형 효율

질문 사양에 따라 안전하고 신뢰할 수 있으며 예측 가능한 재사용 가능한 기능을 위해서는 두 가지 패스가 필요합니다. 재사용 가능한 함수에서 vargs 크기 분포에 대한 가정은 잘못된 프로그래밍 스타일이므로 피해야합니다. 이 경우, 임의의 큰 변수 길이 표현이 알고리즘 선택의 핵심 요소입니다.

오버 플로우시 재 시도는 기하 급수적으로 비효율적이며, 이는 쓰기 버퍼가 널 (null) 일 때 드라 이런을 제공하기 위해 C ++ 11 표준위원회가 상기 제안을 논의했을 때 논의 된 또 다른 이유입니다.

위의 프로덕션 준비 구현에서 첫 번째 실행은 할당 크기를 결정하는 건식 실행입니다. 할당이 발생하지 않습니다. printf 지시문의 구문 분석과 vargs의 읽기는 수십 년 동안 매우 효율적으로 이루어졌습니다. 사소한 경우에 대한 작은 비 효율성이 희생되어야하더라도 재사용 가능한 코드는 예측 가능해야합니다.

보안 및 신뢰성

앤드류 코니 그 (Andrew Koenig)는 케임브리지 행사에서 강연을 마친 후 소규모 그룹에 "사용자 기능이 예외적 인 기능 실패에 대한 착취에 의존해서는 안된다"고 말했다. 평소와 같이, 그의 지혜는 그 이후로 기록에서 사실로 나타났습니다. 수정 및 닫힌 보안 버그 문제는 종종 수정 전에 악용 된 취약점에 대한 설명에서 재시도 해킹을 나타냅니다.

이는 Sprintf 대안, C9X 개정 제안 , ISO IEC 문서 WG14 N645 / X3J11 96-008 의 널 버퍼 기능에 대한 공식 표준 개정 제안에 언급되어 있습니다. 동적 메모리 가용성의 제약 조건 내에서 인쇄 지시문 당 "% s"(으)로 삽입 된 임의로 긴 문자열은 예외가 아니며 "예외 기능"을 생성하기 위해 활용해서는 안됩니다.

이 답변의 첫 번째 단락에 링크 된 C ++ Reference.org 페이지의 맨 아래에 제공된 예제 코드와 함께 제안을 고려하십시오.

또한 실패 사례 테스트는 성공 사례만큼 강력하지 않습니다.

이식성

모든 주요 OS 공급 업체는 c ++ 11 표준의 일부로 std :: vsnprintf를 완벽하게 지원하는 컴파일러를 제공합니다. 더 이상 배포를 유지하지 않는 공급 업체 제품을 실행하는 호스트에는 여러 가지 이유로 g ++ 또는 clang ++이 제공되어야합니다.

스택 사용

std :: vsnprintf에 대한 첫 번째 호출에서 스택 사용은 두 번째 호출보다 작거나 같으며 두 번째 호출이 시작되기 전에 해제됩니다. 첫 번째 호출이 스택 가용성을 초과하면 std :: fprintf도 실패합니다.


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라이브러리는 polyfill이 필요한 경우이를 구현한다고 주장합니다. https://github.com/fmtlib/fmt

C ++ 20 구현 std::format.

sprintf와 같은 std :: string 형식 에서 이전에 언급되었습니다.


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()있던 결과에서 벗어날 필요가 없습니다 .


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

string에는 필요한 것이 없지만 std :: stringstream에는 있습니다. 문자열 스트림을 사용하여 문자열을 만든 다음 문자열을 추출하십시오. 다음 은 수행 할 수있는 작업에 대한 포괄적 인 목록입니다. 예를 들면 다음과 같습니다.

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

double 또는 float를 인쇄 할 때 소수점 이하 10 자리의 정밀도를 제공합니다.


당신은 이것을 시도 할 수 있습니다 :

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

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

이것은 내 프로그램 에서이 작업을 수행하는 데 사용하는 코드입니다 ... 멋진 것은 아니지만 트릭을 수행합니다 ... 참고, 적용 가능한 크기를 조정해야합니다. 나를위한 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;
}

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()초기 버퍼가 이미 충분할 때 두 번째 호출을 방지하기 위해 초기 버퍼를 사용하는 또 다른 버전이 있습니다 .

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의 답변 과 비슷 new하며 delete[], and 만 사용하지 않으며 생성 할 때 크기도 지정합니다 std::string.

여기에 사용하지 않는 생각 newdelete[]는 통화 할당 및 할당 해제 기능을 필요로하지 않기 때문에 제대로 사용하지 그러나 경우, 힙을 통해 스택의 사용을 의미하는 것입니다, 일부 (아마도 이전에 버퍼 오버 플로우 위험, 또는 수 아마도 취약한 시스템 일 것입니다. 이것이 우려되는 경우 newdelete[]대신 사용하는 것이 좋습니다 . 여기서 vsnprintf()제한은 이미 제한으로 호출 된 할당에 관한 것이므로 두 번째 버퍼에 할당 된 크기를 기준으로 제한을 지정하면이를 방지 할 수 있습니다.)


나는 보통 이것을 사용합니다 :

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를 지원하는 것은 아닙니다


@iFreilicht 답변의 약간 수정 된 버전 아래에서 C ++ 14 ( make_unique원시 선언 대신 함수 사용)로 업데이트되고 std::string인수 지원이 추가되었습니다 (Kenny Kerr article 기반 )

#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

원하는 경우이 답변을 원래 답변과 병합하십시오.


Poco Foundation 라이브러리에는 매우 편리한 형식 함수가 있으며 형식 문자열과 값 모두에서 std :: string을 지원합니다.


iomanip 헤더 파일을 사용하여 cout에서 C ++ 출력을 형식화 할 수 있습니다. 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


_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();

매우 간단한 솔루션.

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

업데이트 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);

}

미사용 .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.


몇 가지 대답의 업데이트, 차이점은-함수가 % s에 대해 std :: string을 올바르게 수락 함

namespace format_helper
{

    template <class Src>
    inline Src cast(Src v)
    {
        return v;
    }

    inline const char *cast(const std::string& v)
    {
        return v.c_str();
    }
};

template <typename... Ts>
inline std::string stringfmt (const std::string &fmt, Ts&&... vs)
{
    using namespace format_helper;
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), cast(std::forward<Ts>(vs))...);//not counting the terminating null character.
    std::string result;
    //because we use string as container, it adds extra 0 automatically
    result.resize(required , 0);
    //and snprintf will use n-1 bytes supplied
    std::snprintf(const_cast<char*>(result.data()), required + 1, fmt.c_str(), cast(std::forward<Ts>(vs))...);

    return result;
}

라이브 : http://cpp.sh/5ajsv


내가 선호하는 한 가지 해결책은 sprintf를 사용하여 std :: string 버퍼에 직접 sprintf를 사용하여 버퍼를 충분히 크게 만든 후이 작업을 수행하는 것입니다.

#include <string>
#include <iostream>

using namespace std;

string l_output;
l_output.resize(100);

for (int i = 0; i < 1000; ++i)
{       
    memset (&l_output[0], 0, 100);
    sprintf (&l_output[0], "\r%i\0", i);

    cout << l_output;
    cout.flush();
}

따라서 std :: string을 만들고 크기를 조정하고 버퍼에 직접 액세스하십시오 ...

참고 URL : https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf



반응형