sprintf와 같은 std :: string 형식


454

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


6
긴 이야기 짧은 사용 boost::format(kennytm의 솔루션이 여기에서 사용 ). boost::format이미 C ++ 스트림 연산자도 지원합니다! 예 : cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::format코드 줄이 가장 적습니다. 동료 검토를 거쳐 C ++ 스트림과 잘 통합됩니다.
— Trevor Boyd Smith

@Ockonal — 커뮤니티를 위해 (내 담당자에 대해서는별로 신경 쓰지 않았습니다) 선택을 변경하는 것이 좋습니다. 첫 번째 스 니펫에서 현재 선택된 코드는 임의의 최대 길이를 사용할 때 발생하는 버그를 나타냅니다. 두 번째 스 니펫은 sprintf와 같은 vargs를 사용하려는 언급을 완전히 무시합니다. 깨끗하고 안전하며 C ++ 표준에만 의존하고 테스트되고 주석이 달린 대답 만 선택하는 것이 좋습니다. 그것은 내 것이 아닙니다. 객관적으로 사실입니다. stackoverflow.com/questions/2342162/…를 참조하십시오 .
— Douglas Daseeco

@TrevorBoydSmith a std::format가 C ++ 20 BTW에 추가되었습니다 : stackoverflow.com/a/57286312/895245 Awesome!
— Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 나는 C++20어제 에 관한 기사를 읽었 으며 스펙 에 추가하여 C++20사본을 보았습니다 boost(현재 백만 번째 시간) ! 나는 매우 행복했다! 지난 9 년 동안 작성한 거의 모든 C ++ 파일이 사용되었습니다 . C ++의 스트림에 공식 printf 스타일 출력을 추가하면 모든 C ++에서 먼 길을 갈 것입니다. std::formatC++20boost::format
— Trevor Boyd Smith

답변:


333

기본 버퍼에 대한 쓰기 액세스 권한이 없으므로 직접 수행 할 수 없습니다 (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();

17
마법 쿠키 char buf[100];는이 솔루션을 강력하지 않습니다. 그러나 중요한 아이디어가 있습니다.
— John Dibling

18
존, 스트림은 느리지 않습니다. 스트림이 느린 것처럼 보이는 유일한 이유는 기본적으로 iostream이 C FILE 출력과 동기화되어 혼합 된 cout 및 printfs가 올바르게 출력되기 때문입니다. 이 링크를 비활성화하면 (cout.sync_with_stdio (false)를 호출하여) 적어도 ++ MSVC10에서와 같이 c ++의 스트림이 stdio보다 성능이 뛰어납니다.
— Jimbo

72
형식을 사용하는 이유는 로컬 문법이 문장의 문법을 하드 코딩하는 대신 외국어 문장 구조를 재구성 할 수있게하기 위함입니다.
— Martijn Courteaux

216
어떤 이유로 다른 언어는 printf와 같은 구문을 사용합니다 : Java, Python (새 구문은 스트림보다 printf에 더 가깝습니다). C + +만이 무고한 인간 에게이 장황한 가증을가합니다.
— quant_dev

9
더 좋은 방법 asprintf은 결과를 보유하기에 충분한 공간이있는 새 문자열을 할당하는를 사용 하는 것입니다. 그런 다음 std::string원하는 경우이를 복사 free하고 원본을 기억하십시오 . 또한, 어떤 좋은 컴파일러는 당신을위한 형식을 검증 도움이되도록 매크로에 넣고 할 수 - 당신은 넣어하지 않으 doubleA는 어디에 %s기대된다
— 아론 McDaid

286

현대 C ++은 이것을 매우 간단하게 만듭니다.

C ++ 20

C ++ 20 에는을 도입 std::format하여 정확하게 할 수 있습니다. 파이썬 과 비슷한 대체 필드를 사용합니다 .

#include <iostream>
#include <format>

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

전체 문서를 확인하십시오 ! 그것은 삶의 질을 크게 향상시킵니다.


C ++ 11

로 C ++ 11 개 의 std::snprintf이 이미 꽤 쉽고 안전하게 작업이되었다.

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

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'
    if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    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 배열의 원하는 길이를 결정합니다 snprintf. 에서 cppreference.com :

반환 값

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

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

size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintf오류가 발생하면 음수를 반환하므로 서식이 원하는대로 작동하는지 확인합니다. 이 작업을 수행하지 않으면 주석에서 @ead가 지적한 것처럼 자동 오류 또는 거대한 버퍼 할당이 발생할 수 있습니다.

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

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

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

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

그 후, 우리는 물론 snprintf의도 된 용도로 사용하고 형식화 된 문자열을에 쓸 수 있습니다 char[].

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

마지막으로, 우리는 새로운 것을 생성하고 반환 std::string하여 끝에 null 종결자를 생략합니다.

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

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


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


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

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

#pragma warning(disable : 4996)

마지막 생각들

이 질문에 대한 많은 답변은 C ++ 11 이전에 작성되었으며 고정 버퍼 길이 또는 변수를 사용합니다. 이전 버전의 C ++를 사용하지 않는 한 해당 솔루션을 사용하지 않는 것이 좋습니다. 이상적으로는 C ++ 20으로 이동하십시오.

이 답변의 C ++ 11 솔루션은 템플릿을 사용하므로 많이 사용하면 상당히 많은 코드를 생성 할 수 있습니다. 그러나 바이너리 공간이 매우 제한된 환경을 위해 개발하지 않는 한, 이것은 문제가되지 않으며 명확성과 보안 측면에서 다른 솔루션에 비해 여전히 크게 개선 된 것입니다.

공간 효율성이 매우 중요한 경우 vargs 및 vsnprintf 가 포함 된 이 두 가지 솔루션 이 유용 할 수 있습니다. 버퍼 길이가 고정 된 솔루션을 사용 하면 문제가 발생 하지 않습니다 .


2
VS 버전 이 2013 이상이어야 함을 Visual Studio 사용자의 답변에서 강조하십시오. 이 기사에서 VS2013 버전에서만 작동한다는 것을 알 수 있습니다. buffer가 null 포인터이고 count가 0이면 len은 다음과 같이 반환됩니다. 종료 널을 포함하지 않고 출력을 형식화하는 데 필요한 문자 수 동일한 인수 및 로케일 매개 변수를 사용하여 성공적으로 호출하려면 최소한 len + 1자를 보유하는 버퍼를 할당하십시오.
— cha

3
@moooeeeep 여러 가지 이유. 첫째, 여기서 목표는 c-string이 아닌 std :: string을 반환하는 것이므로 아마도 의미가 return string(&buf[0], size);있거나 비슷한 것입니다. 둘째, c- 문자열을 이런 식으로 반환하면 반환하려는 값을 보유한 벡터가 무효화되므로 정의되지 않은 동작이 발생합니다. 셋째, C ++을 배우기 시작했을 때 표준은 요소 안에 어떤 순서로 요소를 저장해야하는지 정의하지 않았 std::vector으므로 포인터를 통해 저장소에 액세스하는 것은 정의되지 않은 동작입니다. 이제는 효과가 있지만 그렇게하는 데 아무런 이점이 없습니다.
— iFreilicht

2
@iFreilicht std::string암시 적으로 변환 된 벡터 ( 복사 초기화 ) 에서 새로운 것이 만들어 지고, 함수 서명에서 알 수 있듯이 복사본으로 반환됩니다. 또한 a의 요소 std::vector는 항상 연속적으로 저장되도록 설계되었습니다 . 그러나 나는 그렇게 할 때 아무런 이점이 없을 것이라고 주장합니다.
— moooeeeep

4
나는이 솔루션을 정말로 좋아하지만 줄 return string(buf.get(), buf.get() + size);이 return string(buf.get(), buf.get() + size - 1);그렇지 않으면 끝에 널 문자가있는 문자열을 가져야 한다고 생각합니다 . 나는 이것이 gcc 4.9의 경우라고 생각했다.
— Phil Williams

3
std :: string을 % s에 전달하면 컴파일 오류가 발생합니다 ( 오류 : 가변형 함수를 통해 중요하지 않은 유형 'std :: __ cxx11 :: basic_string <char>'의 오브젝트를 전달할 수 없습니다. 런타임시 호출이 중단됩니다. [-Wnon-pod clang 3.9.1에서는 -varargs] )를 사용하지만 CL 19에서는 컴파일이 잘되고 런타임에 충돌이 발생합니다. cl에서 컴파일 타임에 그 경고 메시지를 표시하도록 설정할 수있는 경고 플래그가 있습니까?
— Zitrax

241

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렌더링 속도가 느려집니다.


3
느린. 왜 크기를 1 씩 늘립니까? 이 함수는 언제 -1을 반환합니까?
— 0xDEAD BEEF

27
str.c_str ()을 덮어 쓰고 있습니까? 그렇게 위험하지 않습니까?
— Quantum

8
참조 인수가있는 va_start는 MSVC에 문제가 있습니다. 자동으로 실패하고 임의 메모리에 대한 포인터를 리턴합니다. 임시 해결책으로 std :: string & fmt 대신 std :: string fmt를 사용하거나 랩퍼 오브젝트를 작성하십시오.
— Steve Hanov

6
나는 이것이 아마도 대부분의 std :: strings가 구현되는 방식에 따라 작동한다는 것을 알고 +1 했으므로 c_str은 실제로 기본 문자열을 수정하기위한 장소가 아닙니다. 읽기 전용이어야합니다.
— Doug T.

6
결과 문자열 길이를 미리 얻으려면 다음을 참조하십시오. stackoverflow.com/a/7825892/908336size 첫 번째 호출로 얻을 수있을 때 각 반복 에서 증가하는 요점을 보지 못합니다 vsnprintf().
— Massood Khaari

107

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

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

형식 개체는 형식 문자열로 구성되며 연산자 %를 반복해서 호출하여 인수가 제공됩니다. 그런 다음 각 인수는 문자열로 변환되어 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"

5
필요한 라이브러리를 제거 할 수도 있습니다. 유연한 도구 사용
— Hassan Syed

7
부스트 형식은 클뿐만 아니라 매우 느립니다. zverovich.net/2013/09/07/… 및 boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
— vitaut

14
프로젝트의 아무 곳이나 부스트를 포함하면 컴파일 시간이 크게 늘어납니다. 대규모 프로젝트의 경우 가장 중요하지 않습니다. 소규모 프로젝트의 경우 부스트는 드래그입니다.
— quant_dev

2
@vitaut 대안에 비해 엄청나게 많은 리소스를 소비합니다. 얼마나 자주 문자열을 포맷합니까? 단 몇 초만 걸리고 대부분의 프로젝트는 수십 번만 사용한다고 생각하면 문자열 형식에 중점을 두지 않는 프로젝트에서는 눈에 띄지 않습니까?
— AturSams

2
불행히도 boost :: format은 같은 방식으로 작동하지 않습니다. var_args를 허용하지 않습니다. 어떤 사람들은 하나의 프로그램과 관련된 모든 코드가 동일하거나 같은 관용구를 사용하는 것을 좋아합니다.
— xor007

88

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

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

또는

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

또는 {fmt} 라이브러리 를 사용하여 문자열을 형식화하고 stdout한 번에 파일 스트림 또는 파일 스트림에 쓸 수 있습니다 .

fmt::print(f, "Look, a string: {}", s); // where f is a file stream

sprintf여기 또는 대부분의 다른 답변에 관해서는 불행히도 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

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


IMHO 당신은 포함을 그리워 error: 'fmt' has not been declared
— Sérgio

이것은 완전한 코드가 아닌 스 니펫입니다. 분명히 <fmt / format.h>를 포함시키고 코드를 함수에 넣어야합니다.
— vitaut

나에게 그렇게 분명하지 않다, IMHO 당신은 스 니펫에 그것을 포함시켜야합니다, 피드백 주셔서 감사합니다
— Sérgio

1
fmt구현 등 C ++ 20에 추가되었습니다! stackoverflow.com/a/57286312/895245 fmt는 현재이를 지원한다고 주장합니다. 멋진 일!
— Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

2
@vitaut이 작업에 감사드립니다!
— 커트 니콜스


15

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

이것은 데이터의 추가 사본을 모두 수행 vsnprintf하므로 문자열에 직접 사용할 수 있습니다.
— Mooing Duck

1
결과 문자열 길이를 미리 얻으려면 stackoverflow.com/a/7825892/908336 의 코드를 사용하십시오 . 예외 안전 코드에 스마트 포인터를 사용할 수 있습니다.std::unique_ptr<char[]> buffer (new char[size]);
— Massood Khaari

대체 사례에서 이것이 올바른지 확실하지 않습니다. 인수를 올바르게 보려면 두 번째 vsnprintf ()에 대해 vl의 va_copy를 수행해야한다고 생각합니다. 예를 들어 참조 : github.com/haberman/upb/blob/...
— 조쉬 Haberman

15

std::string'sprintf'방식으로 포맷하려면 snprintf(인수 nullptr및 0)를 호출 하여 필요한 버퍼 길이를 얻으십시오. 다음과 같이 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);

VC ++ 12 (Visual Studio 2013)에서는 std :: snprintf를 사용할 수 없습니다. 대신 _snprintf로 바꾸십시오.
— Shital Shah

왜 char buf[length + 1];대신에 사용 하지 char* buf = new char[length + 1];않습니까?
— Behrouz.M

사용의 차이 char[]및 char*신규로는, 전자의 경우에 버피 스택에 할당 될 수 있다는 것이다. 작은 버퍼에 대해서는 괜찮지 만 결과 문자열의 크기를 보장 할 수 없으므로 사용하는 것이 좋습니다 new. 예를 들어, 내 컴퓨터에서 string_sprintf("value: %020000000d",5), 배열 5를 스택에 사용할 때 코어 덤프 (core dump) 5 번 앞에 터무니없는 선행 0을 인쇄하지만 동적으로 할당 된 배열을 사용할 때는 정상 작동new char[length + 1]
— user2622016

포맷 된 출력에 필요한 실제 버프 크기를 얻는 매우 영리한 아이디어
— Chris

1
@ user2622016 : 솔루션 주셔서 감사합니다! 그주의 std::move 불필요 .
— Mihai Todor 2016 년

14

[편집 : 20/05/25] 더 나은 여전히 ​​... :
헤더 :

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

PRINTSTRING(r)α- 함수는 GUI 또는 터미널 또는 사용 특별한 출력 요구에 대한 수용하는 것입니다 #ifdef _some_flag_, 기본은 다음과 같습니다

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[편집 '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;
}

@MooingDuck : Dan의 의견에 따라 기능 매개 변수가 Aronesty의 답변으로 변경되었습니다. 나는 Linux / gcc 만 사용 fmt하고 참조로 잘 작동합니다. (그러나 나는 사람들이 장난감을 가지고 놀기를 원한다고 생각합니다.) 다른 '버그'가 있다면 정교하게 설명해 주시겠습니까?
— slashmais

나는 그의 코드의 일부가 어떻게 작동했는지 이해하지 못했고 많은 크기 조정을하고 있다고 생각했습니다. 재검토는 내가 착각했음을 보여준다. 코드가 정확합니다.
— Mooing Duck

Erik Aronesty의 답변에서 벗어나는 것은 청어입니다. 그의 첫 번째 코드 샘플은 안전하지 않으며 두 번째 코드 샘플은 비효율적이고 서투른 것입니다. 깨끗한 구현은 vprintf 함수 패밀리 중 하나의 buf_siz가 0이면 아무것도 작성되지 않고 버퍼가 널 포인터 일 수 있지만 반환 값 (포함되지 않은 바이트 수) null 종결 자)는 여전히 계산되어 반환됩니다. 의 제조 품질 답은 여기에 있습니다 : stackoverflow.com/questions/2342162/...
— 더글러스 Daseeco

10

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


10

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

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


1
snprintf는 항상 종료 널 바이트를 추가하지만이를 포함하지 않는 문자 수를 리턴합니다. 이 코드가 항상 마지막 문자를 건너 뛰지 않습니까?
— alexk7

@ alexk7, 멋진 캐치! 답변을 업데이트하고 있습니다. 이 코드는 마지막 문자를 건너 뛰지 않지만 bytes버퍼 끝을 넘어 , 아마도 required정수를 초과하여 씁니다 (다행히 그 시점에서 이미 평가되었습니다).
— Dacav

1
작은 힌트 : 버퍼 크기가 0 인 경우 코드 nullptr에서 char b;줄을 제거하여 버퍼 인수로 a 를 전달할 수 있습니다. ( 출처 )
— iFreilicht 2016 년

@iFreilicht, 수정되었습니다. 또한 +1
— Dacav

2
"char bytes [필수]"를 사용하면 힙 대신 스택에 할당되므로 대형 문자열에서는 위험 할 수 있습니다. 대신 새 것을 사용하십시오. Yann
— Yannuth

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


9

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

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


간단하고 강력합니다. 부적합한 vsnprintf-s가있는 HP-UX, IRIX, Tru64에서는 실패 할 수 있습니다. 편집 : 또한 2 패스가 성능에 미치는 영향을 고려하십시오. 가장 일반적인 작은 문자열 형식의 경우 초기 패스에 대한 추측을 고려한 적이 있습니까?
— 엔지니어

FWIW, 내가 언급 한 추측은 첫 번째 실행이 발생하는 스택 할당 버퍼를 사용합니다. 적합하면 두 번째 실행 비용과 여기에서 발생하는 동적 할당이 절약됩니다. 아마도 작은 줄은 큰 줄보다 더 자주 사용됩니다. 내 조잡한 벤치 마크에서 그 전략 (거의)은 작은 문자열의 실행 시간을 절반으로 줄이고 위 전략의 몇 퍼센트 (고정 된 오버 헤드 일까?) 내에 있습니다. 드라 이런 등을 사용하는 C ++ 11 디자인에 대해 자세히 설명해 주시겠습니까? 그것에 대해 읽고 싶습니다.
— 엔지니어

@Engineerist, 귀하의 질문은 코드의 위와 아래의 답변 본문에서 해결되었습니다. 하위 주제를 쉽게 읽을 수 있습니다.
— Douglas Daseeco

6

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 형식 에서 이전에 언급되었습니다.


5

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


1
Erik Aronesty의 답변에서 벗어나는 것은 청어입니다. 그의 첫 번째 코드 샘플은 안전하지 않으며 두 번째 코드는 루프가 비효율적이고 어색합니다. 깨끗한 구현은 vprintf 함수 패밀리 중 하나의 buf_siz가 0이면 아무것도 작성되지 않고 버퍼가 널 포인터 일 수 있지만 반환 값 (포함되지 않은 바이트 수) null 종결 자)는 여전히 계산되어 반환됩니다. 의 제조 품질 답은 여기에 있습니다 : stackoverflow.com/questions/2342162/...
— 더글러스 Daseeco

Erik Aronesty의 답변은 광산이 추가 된 이후 편집되었습니다. vector <char>를 사용하여 문자열을 빌드 할 때 저장하는 옵션을 강조하고 싶었습니다. C ++ 코드에서 C 함수를 호출 할 때이 기술을 자주 사용합니다. 질문에 34 개의 답변이 있다는 것이 흥미 롭습니다.
— ChetS

vfprintf 페이지의 cppreference.com 예제는 나중에 추가되었습니다. 가장 좋은 대답은 현재 받아 들여지는 대답이며, printf 변형 대신 문자열 스트림을 사용하는 것이 C ++ 방식입니다. 그러나 내 대답은 그것이 제공되었을 때 가치를 추가했습니다. 당시 다른 답변보다 점진적으로 개선되었습니다. 이제 표준에는 string_view, 매개 변수 팩 및 Variadic 템플릿이 있으며 새로운 답변에 이러한 기능이 포함될 수 있습니다. 내 대답은 더 이상 추가 투표를받을 자격이 없지만 삭제되거나 투표를받을 자격이 없으므로 그대로 둡니다.
— ChetS

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

1
현명한 아이디어는 +1이지만 그 내용 _vscprintf이 명확하지 않습니다 . 이 답변에 대해 자세히 설명해야한다고 생각합니다.
— Dacav

3

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

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

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


8
여전히 제어 printf 근처에 아무것도 제공하지 않지만 printf가 제공합니다 ...하지만 좋습니다.
— Erik Aronesty

3

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

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

3

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

2
formatgcc에게 인수의 유형을 확인하고 -Wall로 적절한 경고를하도록 지시 하기 때문에이 행을 선언에 추가 할 것이다 .std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
— Aaron McDaid

2
에 전화를 추가했습니다 va_end. "va_start 또는 va_copy를 호출하는 함수 전에 va_end를 호출하지 않으면 동작이 정의되지 않습니다." - docs
— Aaron McDaid

1
실패시 포인터 값이 정의되지 않았으므로 vasprintf의 리턴 결과를 확인해야합니다. 따라서 <new>를 포함하고 다음을 추가하십시오. if (size == -1) {throw std :: bad_alloc (); }
— Neil McGill

좋은 지적, 그에 따라 답변을 수정 throw std::bad_alloc();했습니다. 코드베이스에서 C ++ 예외를 사용하지 않기 때문에을 수행하는 대신 의견을 작성하기로 결정 했으며 사람들은 쉽게 기반으로 추가 할 수 있습니다 소스 의견과 여기에 귀하의 의견에.
— 토마스 펄

2

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

4
textString의 초기화는 이미 전체 버퍼를 0으로 설정합니다.
— Memset

이것은 데이터의 추가 사본을 모두 수행 vsnprintf하므로 문자열에 직접 사용할 수 있습니다.
— Mooing Duck

2

Dacav 와 pixelpoint의 답변 에서 아이디어를 얻었습니다 . 나는 조금 놀았고 이것을 얻었다 :

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

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


2

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

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


vasprintf는 훌륭하지만 리턴 코드를 확인해야합니다. -1 버퍼에는 정의되지 않은 값이 있습니다. 필요 : if (size == -1) {throw std :: bad_alloc (); }
— Neil McGill

2

@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

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



1

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


1

버퍼가 문자열을 인쇄하기에 충분히 크지 않으면 문제가있을 수 있습니다. 형식화 된 메시지를 인쇄하기 전에 형식화 된 문자열의 길이를 결정해야합니다. 나는 이것에 대한 자체 도우미를 만들고 (Windows 및 Linux GCC 에서 테스트 됨 ) 사용할 수 있습니다.

String.cpp : http://pastebin.com/DnfvzyKP
String.h : http://pastebin.com/7U6iCUMa

String.cpp :

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h :

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

줄과 관련하여 vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);-문자열의 버퍼가 널 문자를 종료 할 공간이 있다고 가정하는 것이 안전합니까? size + 1 문자를 할당하지 않는 구현이 있습니까? 더 안전할까요dst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
— drwatsoncode

분명히 내 이전 의견에 대한 대답은 다음과 같습니다. 아니요 null 문자가 있다고 가정하는 것이 안전하지 않습니다. 특히 C ++ 98 스펙과 관련하여 : "data () + size ()에서 값에 액세스하면 정의되지 않은 동작 이 생성 됩니다. 널 문자가이 함수가 리턴 한 값이 가리키는 문자 순서를 종료한다는 보장 은 없습니다 . 문자열 참조 :: 그러한 보증을 제공하는 기능에 대한 c_str. 프로그램은이 순서대로 문자를 변경하지 않는다. "그러나, C는 ++ (11) 스펙을 나타냅니다 data와 c_str동의어입니다.
— drwatsoncode

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

1

매우 간단한 솔루션.

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

1

나는 이것이 여러 번 대답되었다는 것을 알고 있지만 이것은 더 간결합니다.

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

예:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

http://rextester.com/NJB14150 참조


1

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.