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.