formatação std :: string como sprintf


454

Eu tenho para o formato std::stringcom sprintfe enviá-lo para fluxo de arquivo. Como posso fazer isso?


6
longa história de uso curto boost::format(como a solução da kennytm usa aqui ). boost::formatjá suporta operadores de fluxo C ++ também! exemplo: cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;. boost::formattem o mínimo de linhas de código ... é revisado por pares e se integra perfeitamente aos fluxos de C ++.
Trevor Boyd Smith

@Ockonal - Para o bem da comunidade (eu não poderia me importar menos com meu representante), sugiro que você altere sua seleção. O atualmente selecionado, no primeiro trecho, apresenta um bug aguardando o uso de um comprimento máximo arbitrário. O segundo trecho ignora completamente o desejo declarado de usar vargs como sprintf. Sugiro que você selecione a ÚNICA resposta aqui que é limpa, segura, depende apenas dos padrões C ++, testada e bem comentada. Que é meu não é relevante. É objetivamente verdadeiro. Consulte stackoverflow.com/questions/2342162/… .
Douglas Daseeco 21/09

O @TrevorBoydSmith a std::formatfoi adicionado ao C ++ 20 BTW: stackoverflow.com/a/57286312/895245 Awesome!
Ciro Santilli escreveu:

1
@CiroSantilli eu li um artigo sobre C++20ontem e vi isso C++20copiado boost(pela milionésima vez agora) adicionando o std::formatà C++20especificação! Eu fiquei muito, muito feliz! Quase todos os arquivos C ++ que escrevi nos últimos 9 anos foram usados boost::format. adicionar saída oficial do estilo printf aos fluxos em C ++ irá percorrer um longo caminho IMO para todo o C ++.
Trevor Boyd Smith

Respostas:


333

Você não pode fazer isso diretamente, porque não possui acesso de gravação ao buffer subjacente (até C ++ 11; consulte o comentário de Dietrich Epp ). Você precisará fazer isso primeiro em uma string c, depois copiá-lo em uma std :: string:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Mas não sei por que você não usaria apenas um fluxo de string? Suponho que você tenha razões específicas para não apenas fazer isso:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

17
O cookie mágico char buf[100];torna essa solução não muito robusta. Mas a ideia essencial está lá.
John Dibling

18
John, os fluxos não são lentos. A única razão pela qual os fluxos parecem lentos é que, por padrão, os iostreams estão sincronizando com a saída C FILE, para que cout e printfs misturados sejam impressos corretamente. Desabilitar esse link (com uma chamada para cout.sync_with_stdio (false)) faz com que os fluxos do c ++ superem o stdio, pelo menos a partir do MSVC10.
Jimbo

72
O motivo para usar formatos é permitir que um localizador reconstrua a estrutura da sentença para idiomas estrangeiros, em vez de codificar a gramática da sentença.
Martijn Courteaux

216
Por alguma razão, outras linguagens usam a sintaxe do tipo printf: Java, Python (a nova sintaxe ainda está mais próxima do printf do que dos fluxos). Somente C ++ inflige essa abominação detalhada a seres humanos inocentes.
quant_dev

9
Melhor ainda, use asprintf, que aloca uma nova string com espaço suficiente para armazenar o resultado. Em seguida, copie para um, std::stringse quiser, e lembre-se freedo original. Além disso, é possível colocar isso em uma macro para que qualquer bom compilador irá ajudar a validar o formato para você - você não quer colocar um doubleonde um %sé esperado
Aaron McDaid

286

O C ++ moderno torna isso super simples.

C ++ 20

O C ++ 20 apresenta std::format, o que permite fazer exatamente isso. Ele usa campos de substituição semelhantes aos do python :

#include <iostream>
#include <format>

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

Confira a documentação completa ! É uma enorme melhoria na qualidade de vida.


C ++ 11

Com o C ++ 11 s std::snprintf, isso já se tornou uma tarefa bastante fácil e segura.

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

O trecho de código acima está licenciado sob CC0 1.0 .

Linha por linha explicação:

Objetivo: escreva para achar*usando std::snprintfe depois converta para astd::string.

Em primeiro lugar, determina-se o comprimento desejado da matriz de char usando uma condição especial snprintf. Em cppreference.com :

Valor de retorno

[...] Se a sequência resultante for truncada devido ao limite de buf_size, a função retornará o número total de caracteres (não incluindo o byte nulo final) que teria sido gravado, se o limite não fosse imposto.

Isso significa que o tamanho desejado é o número de caracteres mais um , para que o terminador nulo fique atrás de todos os outros caracteres e que possa ser cortado pelo construtor de cadeias novamente. Esse problema foi explicado por @ alexk7 nos comentários.

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

snprintfretornará um número negativo se ocorrer um erro; portanto, verificamos se a formatação funcionou como desejado. Não fazer isso pode levar a erros silenciosos ou à alocação de um buffer enorme, como apontado pelo @ead nos comentários.

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

Em seguida, alocamos uma nova matriz de caracteres e atribuímos a a std::unique_ptr. Isso geralmente é recomendado, pois você não precisará manualmente manualmente deletenovamente.

Observe que essa não é uma maneira segura de alocar um unique_ptrcom tipos definidos pelo usuário, pois você não pode desalocar a memória se o construtor lançar uma exceção!

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

Depois disso, é claro que podemos apenas usar snprintfpara o uso pretendido e escrever a string formatada no arquivo char[].

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

Por fim, criamos e retornamos um novo std::string, certificando-se de omitir o terminador nulo no final.

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

Você pode ver um exemplo em ação aqui .


Se você também deseja usar std::stringna lista de argumentos, dê uma olhada nesta essência .


Informações adicionais para o Visual Studio usuários do :

Conforme explicado nesta resposta , a Microsoft renomeou std::snprintfpara _snprintf(sim, sem std::). A Microsoft ainda o define como obsoleta e recomenda o uso _snprintf_s, mas _snprintf_snão aceita que o buffer seja zero ou menor que a saída formatada e não calculará o comprimento das saídas, se isso ocorrer. Portanto, para se livrar dos avisos de descontinuação durante a compilação, você pode inserir a seguinte linha na parte superior do arquivo que contém o uso de _snprintf:

#pragma warning(disable : 4996)

Pensamentos finais

Muitas respostas a essa pergunta foram escritas antes do tempo do C ++ 11 e usam comprimentos de buffer fixos ou vargs. A menos que você esteja preso às versões antigas do C ++, eu não recomendaria o uso dessas soluções. Idealmente, siga o caminho C ++ 20.

Como a solução C ++ 11 nesta resposta usa modelos, ela pode gerar um pouco de código se for usada muito. No entanto, a menos que você esteja desenvolvendo para um ambiente com espaço muito limitado para binários, isso não será um problema e ainda será uma grande melhoria em relação às outras soluções, tanto em clareza quanto em segurança.

Se a eficiência do espaço for super importante, essas duas soluções com vargs e vsnprintf podem ser úteis. NÃO USE soluções com comprimentos de buffer fixos, isso está apenas pedindo problemas.


2
Em sua resposta para os usuários do Visual Studio, enfatize que a versão do VS deve ser pelo menos 2013. Neste artigo, você pode ver que ele funciona apenas com a versão VS2013: Se o buffer for um ponteiro nulo e a contagem for zero, len será retornado como a contagem de caracteres necessários para formatar a saída, sem incluir o nulo final. Para fazer uma chamada bem-sucedida com o mesmo argumento e parâmetros de localidade, aloque um buffer que contenha pelo menos len + 1 caracteres.
cha

3
@moooeeeep Várias razões. Primeiramente, o objetivo aqui é retornar um std :: string, não um c-string, então você provavelmente quis dizer return string(&buf[0], size);algo parecido. Em segundo lugar, se você retornasse uma string c assim, isso causaria um comportamento indefinido porque o vetor que contém os valores para os quais você aponta será invalidado no retorno. Em terceiro lugar, quando comecei a aprender C ++, o padrão não definiu em que ordem os elementos deveriam ser armazenados dentro de um std::vector, portanto, acessar seu armazenamento por meio de um ponteiro era um comportamento indefinido. Agora funcionaria, mas não vejo benefício em fazê-lo dessa maneira.
IFreilicht 25/04

2
@iFreilicht Um novo std::stringserá construído a partir do vetor implicitamente convertido ( inicialização da cópia ), que será retornado como uma cópia, como sugere a assinatura da função. Além disso, os elementos de a std::vectorsão e sempre foram destinados a serem armazenados contiguamente . Mas entendo que talvez não haja benefício em fazê-lo.
moooeeeep

4
Eu realmente gosto dessa solução, no entanto, acho que a linha return string(buf.get(), buf.get() + size);deveria ser return string(buf.get(), buf.get() + size - 1);outra, você recebe uma string com um caractere nulo no final. Achei que esse fosse o caso no gcc 4.9.
Phil Williams

3
Passar um std :: string para% s causa um erro de compilação ( erro: não é possível transmitir objeto do tipo não trivial 'std :: __ cxx11 :: basic_string <char>' através da função variadic; a chamada será interrompida no tempo de execução [-Wnon-pod -varargs] ) no clang 3.9.1, mas no CL 19 ele compila bem e trava no tempo de execução. Algum sinalizador de aviso que eu possa acender para que também cought em tempo de compilação?
Zitrax

241

Solução C ++ 11 que usa vsnprintf()internamente:

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

Uma abordagem mais segura e eficiente (eu testei e é mais rápida):

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

O fmt_strvalor é passado em conformidade com os requisitos deva_start .

NOTA: A versão "mais segura" e "mais rápida" não funciona em alguns sistemas. Portanto, ambos ainda estão listados. Além disso, "mais rápido" depende inteiramente da etapa de pré-alocação estar correta, caso contrário, a strcpytorna mais lenta.


3
lento. por que aumentar o tamanho em 1? E quando essa função retorna -1?
0xDEAD BEEF

27
Você está substituindo str.c_str ()? Isso não é perigoso?
Quantum

8
va_start com um argumento de referência tem problemas no MSVC. Ele falha silenciosamente e retorna os ponteiros para a memória aleatória. Como solução alternativa, use std :: string fmt em vez de std :: string & fmt ou escreva um objeto wrapper.
Steve Hanov

6
Eu fiz com +1 porque sei que isso provavelmente funcionará com base na maneira como a maioria das std :: strings são implementadas, no entanto, o c_str não pretende ser um local para modificar a string subjacente. É suposto ser somente leitura.
Doug T.

6
E para obter o comprimento da string resultante anteriormente, consulte: stackoverflow.com/a/7825892/908336 Não vejo sentido em aumentar a sizecada iteração, quando você pode obtê-lo pela primeira chamada de vsnprintf().
Massood Khaari

107

boost::format() fornece a funcionalidade que você deseja:

A partir da sinopse das bibliotecas no formato Boost:

Um objeto de formato é construído a partir de uma string de formato e recebe argumentos por meio de chamadas repetidas ao operador%. Cada um desses argumentos é então convertido em strings, que por sua vez são combinados em uma string, de acordo com a string de formato.

#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
você também pode remover as bibliotecas de que precisa. Usando uma ferramenta fornecida.
Hassan Syed

7
O Boost Format não é apenas grande, mas também muito lento. Veja zverovich.net/2013/09/07/… e boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
Incluir o aumento em qualquer lugar do seu projeto aumenta imediatamente significativamente os tempos de compilação. Para projetos grandes, provavelmente não importa. Para pequenos projetos, o impulso é uma chatice.
quant_dev

2
@vitaut Embora consome muito recursos quando comparado às alternativas. Com que frequência você formata seqüências de caracteres? Considerando que leva apenas alguns microssegundos e a maioria dos projetos provavelmente o usa apenas algumas dezenas de vezes, isso não é perceptível em um projeto que não se concentra muito na formatação de strings, certo?
AturSams 17/08/2015

2
Infelizmente, o boost :: format não funciona da mesma maneira: não aceita os var_args. Algumas pessoas gostam de ter todo o código relacionado a um único programa parecendo o mesmo / usando os mesmos idiomas.
xor007

88

O C ++ 20 incluirá o std::formatque se assemelha sprintfem termos de API, mas é totalmente seguro para o tipo, funciona com tipos definidos pelo usuário e usa sintaxe de cadeia de formato semelhante ao Python. Veja como você poderá formatá std::string-lo e gravá-lo em um fluxo:

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

ou

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

Como alternativa, você pode usar a biblioteca {fmt} para formatar uma sequência e gravá-la stdoutou transmitir um arquivo de uma só vez:

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

Quanto à sprintfmaioria das outras respostas aqui, infelizmente, elas usam varargs e são inerentemente inseguras, a menos que você use algo como o formatatributo do GCC, que só funciona com strings de formato literal. Você pode ver por que essas funções não são seguras no seguinte exemplo:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

onde string_formatestá uma implementação da resposta do Erik Aronesty. Esse código é compilado, mas provavelmente trava quando você tenta executá-lo:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Isenção de responsabilidade : sou o autor de {fmt} e C ++ 20 std::format.


IMHO você perdeu a inclusão error: 'fmt' has not been declared
Sérgio

Este é apenas um trecho, não um código completo. Obviamente, você precisa incluir <fmt / format.h> e colocar o código em uma função.
Vitaut

para mim não é tão óbvio, IMHO, você deve incluí-lo no snippet, obrigado pelo feedback.
Sérgio

1
Uma fmtimplementação semelhante foi adicionada ao C ++ 20! O stackoverflow.com/a/57286312/895245 fmt atualmente reivindica suporte para ele. Otimo trabalho!
Ciro Santilli escreveu:

2
@vitaut Obrigado pelo seu trabalho nisso!
Curt Nichols


15

Eu escrevi meu próprio usando vsnprintf, para que ele retorne uma string em vez de ter que criar meu próprio buffer.

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

Então você pode usá-lo como

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

Isso faz uma cópia extra completa dos dados, é possível usar vsnprintfdiretamente na string.
Mooing Duck

1
Use o código em stackoverflow.com/a/7825892/908336 para obter o comprimento da sequência resultante antecipadamente. E você pode usar ponteiros inteligentes para um código de excepção de segurança:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

Não tenho certeza se isso está correto no caso de fallback; Eu acho que você precisa fazer uma va_copy de vl para o segundo vsnprintf () para ver os argumentos corretamente. Para obter um exemplo, consulte: github.com/haberman/upb/blob/…
Josh Haberman

15

Para formatar std::stringde forma 'sprintf', chame snprintf(argumentos nullptre 0) para obter o tamanho do buffer necessário. Escreva sua função usando o modelo variável C ++ 11 como este:

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

Compile com suporte ao C ++ 11, por exemplo, no GCC: g++ -std=c++11

Uso:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

std :: snprintf não está disponível no VC ++ 12 (Visual Studio 2013). Substitua-o por _snprintf.
Shital Shah

por que você não usa em char buf[length + 1];vez de char* buf = new char[length + 1];?
Behrouz.M

A diferença entre usar char[]e char*com o novo é que, no primeiro caso, o buf seria alocado na pilha. É bom para buffers pequenos, mas como não podemos garantir o tamanho da string resultante, é um pouco melhor usá-lo new. Por exemplo na minha máquina string_sprintf("value: %020000000d",5), imprimir o número exorbitante de zeros antes do número 5, core dumps quando usando um array na pilha, mas funciona bem quando se utiliza alocada dinamicamente variedadenew char[length + 1]
user2622016

idéia muito inteligente para obter o tamanho real lustre necessário para saída formatada
Chris

1
@ user2622016: Obrigado pela solução! Por favor, note que std::move é supérfluo .
Mihai Todor

14

[edit: 20/05/25] melhor ainda ...:
No cabeçalho:

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

A PRINTSTRING(r)função-é atender à GUI ou ao terminal ou a qualquer saída especial que seja necessária #ifdef _some_flag_, o padrão é:

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

[edit '17 / 8/31] Adicionando uma versão com modelo variável '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;
}

que é efetivamente uma versão delimitada por vírgula (em vez) dos <<operadores às vezes dificultadores , usados ​​assim:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[editar] Adaptado para fazer uso da técnica na resposta de Erik Aronesty (acima):

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

[resposta anterior]
Uma resposta muito tardia, mas para quem, como eu, gosta do jeito 'sprintf': escrevi e estou usando as seguintes funções. Se você gostar, poderá expandir as opções%-para se ajustarem melhor às opções do sprint; os que estão lá atualmente são suficientes para minhas necessidades. Você usa stringf () e stringfappend () da mesma forma que usaria o sprintf. Lembre-se de que os parâmetros para ... devem ser do tipo 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: parâmetro de função alterado conforme o comentário de Dan para a resposta do Aronesty. Eu uso apenas Linux / gcc e, fmtcomo referência, funciona bem. (Mas suponho que as pessoas vão querer brincar com brinquedos, então ...) Se houver outros supostos 'bugs', você poderia elaborar?
slashmais

Entendi mal como parte de seu código funcionava e pensei que estava fazendo muitos redimensionamentos. O reexame mostra que eu estava enganado. Seu código está correto.
Mooing Duck

Construir fora da resposta de Erik Aronesty é um arenque vermelho. Seu primeiro exemplo de código é inseguro e o segundo é ineficiente e desajeitado. A implementação limpa é claramente indicada pelo fato de que, se o buf_siz de qualquer família de funções vprintf for zero, nada será gravado e o buffer poderá ser um ponteiro nulo, no entanto, o valor de retorno (número de bytes que seriam gravados sem incluir o terminador nulo) ainda é calculado e retornado. A resposta de qualidade de produção é aqui: stackoverflow.com/questions/2342162/...
Douglas Daseeco

10

É assim que o Google faz: StringPrintf(Licença BSD)
e o Facebook fazem de maneira bastante semelhante: StringPrintf(Licença Apache)
Ambos fornecem um conveniente StringAppendFtambém.


10

Meus dois centavos nesta questão muito popular.

Para citar a página de manual de printffunções like :

Após o retorno bem-sucedido, essas funções retornam o número de caracteres impressos (excluindo o byte nulo usado para finalizar a saída em seqüências de caracteres).

As funções snprintf () e vsnprintf () não gravam mais que bytes de tamanho (incluindo o byte nulo final ('\ 0')). Se a saída foi truncada devido a esse limite, o valor de retorno é o número de caracteres (excluindo o byte nulo final) que teriam sido gravados na sequência final se houvesse espaço suficiente disponível. Assim, um valor de retorno de tamanho ou mais significa que a saída foi truncada.

Em outras palavras, uma implementação sã do C ++ 11 deve ser a seguinte:

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

Funciona muito bem :)

Modelos variáveis ​​são suportados apenas no C ++ 11. A resposta do pixelpoint mostra uma técnica semelhante usando estilos de programação mais antigos.

É estranho que o C ++ não tenha essa coisa pronta para uso. Eles recentemente acrescentaram to_string(), o que, na minha opinião, é um grande passo à frente. Gostaria de saber se eles irão adicionar um .formatoperador para o std::stringeventualmente ...

Editar

Como alexk7 apontou, A +1é necessário no valor de retorno de std::snprintf, pois precisamos ter espaço para o \0byte. Intuitivamente, na maioria das arquiteturas que faltam +1, o requirednúmero inteiro será parcialmente substituído por a 0. Isso acontecerá após a avaliação de requiredcomo parâmetro real para std::snprintf, portanto, o efeito não deve ser visível.

No entanto, esse problema pode mudar, por exemplo, com a otimização do compilador: e se o compilador decidir usar um registro para a requiredvariável? Esse é o tipo de erro que às vezes resulta em problemas de segurança.


1
snprintf sempre acrescenta um byte nulo final, mas retorna o número de caracteres sem ele. Esse código nem sempre pula o último caractere?
alexk7

@ alexk7, boa captura! Estou atualizando a resposta. O código não ignora o último caractere, mas grava além do final do bytesbuffer, provavelmente sobre o requirednúmero inteiro (que felizmente nesse ponto já está avaliado).
Dacav

1
Apenas uma pequena dica: com um tamanho de buffer 0, você pode passar um nullptrargumento como buffer, eliminando a char b;linha no seu código. ( Fonte )
iFreilicht

@iFreilicht, fix'd. Também +1
Dacav 22/03/2015

2
O uso de "char bytes [obrigatório]" será alocado na pilha em vez da pilha, e pode ser perigoso em seqüências de caracteres de grande formato. Considere usar um novo. 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;
}

Usando C99 snprintf e C ++ 11


9

Testado, resposta de qualidade da produção

Esta resposta lida com o caso geral com técnicas compatíveis com os padrões. A mesma abordagem é dada como um exemplo no CppReference.com, na parte inferior da página. Ao contrário do exemplo, esse código se encaixa nos requisitos da pergunta e é testado em campo em robótica e aplicações de satélite. Também melhorou os comentários. A qualidade do design é discutida mais adiante.

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

Eficiência Linear Previsível

Duas passagens são necessárias para uma função reutilizável segura, confiável e previsível, de acordo com as especificações da pergunta. Pressupostos sobre a distribuição de tamanhos de vargs em uma função reutilizável é um estilo de programação ruim e devem ser evitados. Nesse caso, representações arbitrariamente grandes de comprimento variável de vargs são um fator-chave na escolha do algoritmo.

Tentar novamente com estouro excessivo é exponencialmente ineficiente, que é outro motivo discutido quando o comitê de padrões do C ++ 11 discutiu a proposta acima para fornecer uma execução a seco quando o buffer de gravação é nulo.

Na implementação pronta para produção acima, a primeira execução é uma execução tão seca para determinar o tamanho da alocação. Nenhuma alocação ocorre. A análise das diretivas printf e a leitura de vargs tornaram-se extremamente eficientes ao longo de décadas. O código reutilizável deve ser previsível, mesmo que uma pequena ineficiência para casos triviais deva ser sacrificada.

Segurança e Confiabilidade

Andrew Koenig disse a um pequeno grupo de nós após sua palestra em um evento de Cambridge: "As funções do usuário não devem contar com a exploração de uma falha por funcionalidades não excepcionais". Como sempre, sua sabedoria se mostrou verdadeira desde então. Problemas de bug de segurança corrigidos e fechados geralmente indicam novas tentativas de invasão na descrição do furo explorado antes da correção.

Isso é mencionado na proposta formal de revisão de padrões para o recurso de buffer nulo em Alternativa ao sprintf, Proposta de revisão C9X , Documento ISO IEC WG14 N645 / X3J11 96-008 . Uma cadeia arbitrariamente longa inserida por diretiva de impressão, "% s", dentro das restrições de disponibilidade de memória dinâmica, não é uma exceção e não deve ser explorada para produzir "Funcionalidade excepcional".

Considere a proposta ao lado do código de exemplo fornecido na parte inferior da página do C ++ Reference.org vinculado no primeiro parágrafo desta resposta.

Além disso, o teste de casos de falha raramente é tão robusto em casos de sucesso.

Portabilidade

Todos os principais fornecedores de sistemas operacionais fornecem compiladores que suportam totalmente o std :: vsnprintf como parte dos padrões do c ++ 11. Os hosts que executam produtos de fornecedores que não mantêm mais distribuições devem ser fornecidos com g ++ ou clang ++ por vários motivos.

Uso da pilha

O uso da pilha na 1ª chamada para std :: vsnprintf será menor ou igual ao da 2ª e será liberado antes do início da 2ª chamada. Se a primeira chamada exceder a disponibilidade da pilha, o std :: fprintf também falhará.


Breve e robusto. Pode falhar no HP-UX, IRIX, Tru64 que possuem vsnprintf-s não conformes. EDIT: também, considerando como as duas passagens podem afetar o desempenho, esp. para a formatação mais comum de pequenas seqüências de caracteres, você considerou um palpite para o passe inicial, que pode ser suficientemente grande?
Engineer

FWIW, a suposição a que me referi usa um buffer alocado por pilha onde a primeira execução ocorre. Se adequado, ele economiza o custo de uma segunda execução e a alocação dinâmica que ocorre lá. Presumivelmente, cordas pequenas são usadas com mais frequência do que cordas grandes. No meu benchmark bruto, essa estratégia (quase) reduz pela metade o tempo de execução para pequenas cadeias de caracteres e está a poucos por cento (sobrecarga fixa talvez?) Da estratégia acima. Você poderia elaborar o design do C ++ 11 que emprega um funcionamento a seco, etc.? Eu gostaria de ler sobre isso.
Engineer

@ Engenheiro, suas perguntas foram abordadas no corpo da resposta, acima e abaixo do código. Os subtópicos podem ficar mais fáceis de ler dessa maneira.
Douglas Daseeco

6

C ++ 20 std::format

Chegou! O recurso é descrito em: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html e usa uma .format()sintaxe semelhante ao Python .

Espero que o uso seja como:

#include <format>
#include <string>

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

Vou tentar quando o suporte chegar ao GCC, o GCC 9.1.0 com g++-9 -std=c++2aainda não o suporta.

A API adicionará um novo std::formatcabeçalho:

A API de formatação proposta é definida no novo cabeçalho <format>e não deve ter impacto no código existente.

A fmtbiblioteca existente afirma implementá-lo se você precisar do polyfill: https://github.com/fmtlib/fmt

Implementação de C ++ 20 std::format.

e foi mencionado anteriormente em: std :: string formatating like sprintf


5

Com base na resposta fornecida por 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();
}

Isso evita a necessidade de se afastar constdo resultado .c_str()que estava na resposta original.


1
Construir fora da resposta de Erik Aronesty é um arenque vermelho. Seu primeiro exemplo de código é inseguro e o segundo, com o loop, é ineficiente e desajeitado. A implementação limpa é claramente indicada pelo fato de que, se o buf_siz de qualquer família de funções vprintf for zero, nada será gravado e o buffer poderá ser um ponteiro nulo, no entanto, o valor de retorno (número de bytes que seriam gravados sem incluir o terminador nulo) ainda é calculado e retornado. A resposta de qualidade de produção é aqui: stackoverflow.com/questions/2342162/...
Douglas Daseeco

A resposta de Erik Aronesty foi editada desde que a minha foi adicionada. Eu queria destacar a opção de usar o vetor <char> para armazenar as strings à medida que elas são construídas. Eu uso essa técnica frequentemente ao chamar funções C do código C ++. É interessante que a pergunta agora tenha 34 respostas.
ChetS

O exemplo cppreference.com na página vfprintf foi adicionado mais tarde. Eu acredito que a melhor resposta é a resposta atualmente aceita, usando fluxos de string em vez de uma variante printf é a maneira C ++. No entanto, minha resposta agregou valor quando foi fornecida; Era incrementalmente melhor do que outras respostas na época. Agora, o padrão possui string_view, pacotes de parâmetros e modelo Variadic. Uma nova resposta pode incluir esses recursos. Quanto à minha resposta, embora possa não mais merecer mais votos positivos, ele não merece ser excluído ou votado para baixo, por isso estou deixando como está.
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 para a ideia inteligente, mas não está muito claro o que _vscprintfé. Eu acho que você deveria elaborar esta resposta.
Dacav

3

string não tem o que você precisa, mas std :: stringstream possui. Use um stringstream para criar a string e depois extraí-la. Aqui está uma lista abrangente das coisas que você pode fazer. Por exemplo:

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

fornecerá 10 casas decimais de precisão ao imprimir um double ou float.


8
o que ainda não oferece nada próximo ao controle printf, mas é legal.
Erik Aronesty

3

Você pode tentar o seguinte:

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

Se você estiver em um sistema que possua asprintf (3) , poderá envolvê-lo facilmente:

#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
Gostaria de acrescentar esta linha como uma declaração antes format, como diz gcc para verificar os tipos de argumentos e dar um aviso decente com -Wall:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
Acabei de adicionar uma chamada para va_end. "Se va_end não é chamado antes de uma função que chama va_start ou retorna va_copy, o comportamento é indefinido." - docs
Aaron McDaid

1
Você deve verificar o resultado de retorno do vasprintf, pois o valor do ponteiro é indefinido em caso de falha. Portanto, inclua <new> e adicione: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

Bom ponto, modifiquei a resposta de acordo, decidi colocar um comentário lá em vez de fazer o throw std::bad_alloc();, pois não estou usando exceções de C ++ na minha base de código e, para as pessoas que o fazem, podem adicioná-lo facilmente com base no comentário de origem e seu comentário aqui.
Thomas Perl

2

Este é o código que eu uso para fazer isso no meu programa ... Não é nada chique, mas faz o truque ... Nota, você terá que ajustar seu tamanho conforme aplicável. MAX_BUFFER para mim é 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
A inicialização do textString já define o buffer inteiro como zero. Não há necessidade de memset ...
EricSchaefer

Isso faz uma cópia extra completa dos dados, é possível usar vsnprintfdiretamente na string.
Mooing Duck

2

Tomou a idéia da resposta de Dacav e pixelpoint . Eu brinquei um pouco e consegui isso:

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

Com a prática de programação sã, acredito que o código deve ser suficiente, no entanto, ainda estou aberto a alternativas mais seguras que ainda são simples o suficiente e não exigem C ++ 11.


E aqui está outra versão que utiliza um buffer inicial para impedir a segunda chamada vsnprintf()quando o buffer inicial já é suficiente.

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

(Acontece que esta versão é semelhante à resposta de Piti Ongmongkolkul , apenas que ela não usa newe delete[], e também especifica um tamanho ao criar std::string.

A idéia aqui de não usar newe delete[]é implicar o uso da pilha sobre o heap, pois ele não precisa chamar funções de alocação e desalocação; no entanto, se não for usado corretamente, pode ser perigoso armazenar buffer em alguns (talvez antigos ou talvez apenas vulneráveis). Se isso é uma preocupação, eu sugiro usar newe em delete[]vez disso. Observe que a única preocupação aqui é sobre as alocações, como vsnprintf()já é chamado com limites, portanto, especificar um limite com base no tamanho alocado no segundo buffer também os impediria.)


2

Eu costumo usar isso:

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

Desvantagem: nem todos os sistemas suportam vasprint


vasprintf é legal - no entanto, você precisa verificar o código de retorno. On -1 buffer terá um valor indefinido. Necessário: if (size == -1) {throw std :: bad_alloc (); }
Neil McGill

2

Abaixo, a versão ligeiramente modificada da resposta @iFreilicht, atualizada para C ++ 14 (uso da make_uniquefunção em vez da declaração bruta) e suporte adicional para std::stringargumentos (com base no artigo de Kenny Kerr )

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

Resultado:

i = 3, f = 5.000000, s = hello world

Sinta-se à vontade para mesclar esta resposta com a original, se desejar.



1

Você pode formatar a saída C ++ no cout usando o arquivo de cabeçalho iomanip. Certifique-se de incluir o arquivo de cabeçalho iomanip antes de usar qualquer uma das funções auxiliares, como setprecision, setfill etc.

Aqui está um trecho de código que eu usei no passado para imprimir o tempo médio de espera no vetor, que eu "acumulei".

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

Aqui está uma breve descrição de como podemos formatar fluxos C ++. http://www.cprogramming.com/tutorial/iomanip.html


1

Pode haver problemas, se o buffer não for grande o suficiente para imprimir a sequência. Você deve determinar o comprimento da sequência formatada antes de imprimir uma mensagem formatada. Eu faço próprio auxiliar para isso (testado no Windows e Linux GCC ), e você pode tentar usá-lo.

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

Com relação à linha vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- É seguro assumir que o buffer da string tem espaço para um caractere nulo final? Existem implementações que não alocam tamanho + 1 caracteres. Seria mais seguro fazer #dst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

Aparentemente, a resposta para o meu comentário anterior é: Não, NÃO é seguro assumir que existe um caractere nulo. Especificamente com relação à especificação C ++ 98: "O acesso ao valor em data () + size () produz um comportamento indefinido : não há garantias de que um caractere nulo encerre a sequência de caracteres apontada pelo valor retornado por esta função. Consulte a string :: c_str para uma função que fornece essa garantia. Um programa não deve alterar nenhum dos caracteres nesta sequência. "No entanto, a especificação C ++ 11 indica isso datae c_stré sinônimo.
drwatsoncode

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

1

Solução muito, muito simples.

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

1

Sei que isso foi respondido muitas vezes, mas isso é mais conciso:

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

exemplo:

#include <iostream>
#include <random>

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

Veja também http://rextester.com/NJB14150


1

ATUALIZAÇÃO 1 : adicionadafmt::format testes

Eu levei minha própria investigação em torno dos métodos introduzidos aqui e obtive resultados diametralmente opostos versus os mencionados aqui.

Eu usei 4 funções em 4 métodos:

  • função variadica + vsnprintf +std::unique_ptr
  • função variadica + vsnprintf +std::string
  • função variável do modelo + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatfunção da fmtbiblioteca

Para o back-end de teste, o googletestusado.

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

A for_eachimplementação é retirada daqui: itere sobre a tupla

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

}

Os testes:

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

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

}

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

}

RESULTADOS :

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

Como você pode ver, a implementação pelo vsnprintf+ std::stringé igual a fmt::format, mas mais rápida que pelo vsnprintf+ std::unique_ptr, que é mais rápido que pelostd::ostringstream .

Os testes foram compilados Visual Studio 2015 Update 3e executados em Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.