sprintf와 같은 std :: string 형식


454

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


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(인수 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);

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

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()제한은 이미 제한으로 호출 된 할당에 관한 것이므로 두 번째 버퍼에 할당 된 크기를 기준으로 제한을 지정하면이를 방지 할 수 있습니다.)


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) 스펙을 나타냅니다 datac_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::stringfmt::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.