Pergunta

Estou trabalhando com uma ferramenta UNIX de código aberto implementada em C++ e preciso alterar alguns códigos para que ela faça o que desejo.Eu gostaria de fazer a menor alteração possível na esperança de que meu patch seja aceito no upstream.São preferidas soluções que sejam implementáveis ​​em C++ padrão e que não criem mais dependências externas.

Aqui está o meu problema.Eu tenho uma classe C++ - vamos chamá-la de "A" - que atualmente usa fprintf() para imprimir suas estruturas de dados fortemente formatadas em um ponteiro de arquivo.Em sua função de impressão, ele também chama recursivamente as funções de impressão definidas de forma idêntica de várias classes membros ("B" é um exemplo).Há outra classe C que possui um membro std::string "foo" que precisa ser definido como os resultados print() de uma instância de A.Pense nisso como uma função membro to_str() para A.

Em pseudocódigo:

class A {
public:
  ...

  void print(FILE* f);
  B b;

  ...  
};

...

void A::print(FILE *f)
{
  std::string s = "stuff";
  fprintf(f, "some %s", s);
  b.print(f);
}

class C {
  ...
  std::string foo;
  bool set_foo(std::str);
  ...
}

...

A a = new A();
C c = new C();

...

// wish i knew how to write A's to_str()
c.set_foo(a.to_str());

Devo mencionar que C é bastante estável, mas A e B (e o restante dos dependentes de A) estão em um estado de fluxo, portanto, quanto menos alterações de código forem necessárias, melhor.A interface print(FILE* F) atual também precisa ser preservada.Considerei diversas abordagens para implementar A::to_str(), cada uma com vantagens e desvantagens:

  1. Mude as chamadas de fprintf() para sprintf()

    • Eu não teria que reescrever nenhuma string de formato
    • print() poderia ser reimplementado como:fprint(f, this.to_str());
    • Mas eu precisaria alocar manualmente char[]s, mesclar muitas c strings e, finalmente, converter a matriz de caracteres em std::string
  2. Tente capturar os resultados de a.print() em um fluxo de string

    • Eu teria que converter todas as strings de formato para << formato de saída.Existem centenas de fprintf()s para converter :-{
    • print() teria que ser reescrito porque não existe uma maneira padrão que eu conheça para criar um fluxo de saída a partir de um identificador de arquivo UNIX (embora esse cara diz que pode ser possível).
  3. Use a string do Boost biblioteca de formatos

    • Mais dependências externas.Que nojo.
    • A sintaxe do formato é diferente o suficiente de printf() para ser irritante:

    printf(format_str, args) -> cout << boost::format(format_str) % arg1 % arg2 % etc.

  4. Usar Qts QString::asprintf()

    • Uma dependência externa diferente.

Então, esgotei todas as opções possíveis?Se sim, qual você acha que é minha melhor aposta?Se não, o que esqueci?

Obrigado.

Foi útil?

Solução

Estou usando o número 3:a biblioteca de formatos de string boost - mas devo admitir que nunca tive nenhum problema com as diferenças nas especificações de formato.

Funciona perfeitamente para mim - e as dependências externas poderiam ser piores (uma biblioteca muito estável)

Editado:adicionando um exemplo de como usar boost::format em vez de printf:

sprintf(buffer, "This is a string with some %s and %d numbers", "strings", 42);

seria algo assim com a biblioteca boost::format:

string = boost::str(boost::format("This is a string with some %s and %d numbers") %"strings" %42);

Espero que isso ajude a esclarecer o uso de boost::format

Usei boost::format como substituto de sprintf/printf em 4 ou 5 aplicativos (gravando strings formatadas em arquivos ou saída personalizada em arquivos de log) e nunca tive problemas com diferenças de formato.Pode haver alguns especificadores de formato (mais ou menos obscuros) que são diferentes - mas nunca tive problemas.

Em contraste, eu tinha algumas especificações de formato que não poderia fazer com streams (tanto quanto me lembro)

Outras dicas

Aqui está o idioma que eu gosto para tornar a funcionalidade idêntica a 'sprintf', mas retornando um std::string e imune a problemas de buffer overflow.Este código faz parte de um projeto de código aberto que estou escrevendo (licença BSD), então todos fiquem à vontade para usá-lo como desejarem.

#include <string>
#include <cstdarg>
#include <vector>
#include <string>

std::string
format (const char *fmt, ...)
{
    va_list ap;
    va_start (ap, fmt);
    std::string buf = vformat (fmt, ap);
    va_end (ap);
    return buf;
}



std::string
vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.
    size_t size = 1024;
    char buf[size];

    // Try to vsnprintf into our buffer.
    va_list apcopy;
    va_copy (apcopy, ap);
    int needed = vsnprintf (&buf[0], size, fmt, ap);
    // NB. On Windows, vsnprintf returns -1 if the string didn't fit the
    // buffer.  On Linux & OSX, it returns the length it would have needed.

    if (needed <= size && needed >= 0) {
        // It fit fine the first time, we're done.
        return std::string (&buf[0]);
    } else {
        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So do a malloc of the right size and try again.
        // This doesn't happen very often if we chose our initial size
        // well.
        std::vector <char> buf;
        size = needed;
        buf.resize (size);
        needed = vsnprintf (&buf[0], size, fmt, apcopy);
        return std::string (&buf[0]);
    }
}

EDITAR:quando escrevi este código, não tinha ideia de que isso exigia conformidade com C99 e que o Windows (assim como o glibc mais antigo) tinha um comportamento vsnprintf diferente, no qual retorna -1 em caso de falha, em vez de uma medida definitiva de quanto espaço é necessário .Aqui está meu código revisado, todos poderiam dar uma olhada e se vocês acharem que está tudo bem, irei editar novamente para torná-lo o único custo listado:

std::string
Strutil::vformat (const char *fmt, va_list ap)
{
    // Allocate a buffer on the stack that's big enough for us almost
    // all the time.  Be prepared to allocate dynamically if it doesn't fit.
    size_t size = 1024;
    char stackbuf[1024];
    std::vector<char> dynamicbuf;
    char *buf = &stackbuf[0];
    va_list ap_copy;

    while (1) {
        // Try to vsnprintf into our buffer.
        va_copy(ap_copy, ap);
        int needed = vsnprintf (buf, size, fmt, ap);
        va_end(ap_copy);

        // NB. C99 (which modern Linux and OS X follow) says vsnprintf
        // failure returns the length it would have needed.  But older
        // glibc and current Windows return -1 for failure, i.e., not
        // telling us how much was needed.

        if (needed <= (int)size && needed >= 0) {
            // It fit fine so we're done.
            return std::string (buf, (size_t) needed);
        }

        // vsnprintf reported that it wanted to write more characters
        // than we allotted.  So try again using a dynamic buffer.  This
        // doesn't happen very often if we chose our initial size well.
        size = (needed > 0) ? (needed+1) : (size*2);
        dynamicbuf.resize (size);
        buf = &dynamicbuf[0];
    }
}

Você pode usar std::string e iostreams com formatação, como a chamada setw() e outras em iomanip

O seguinte pode ser uma solução alternativa:

void A::printto(ostream outputstream) {
    char buffer[100];
    string s = "stuff";
    sprintf(buffer, "some %s", s);
    outputstream << buffer << endl;
    b.printto(outputstream);
}

(B::printto semelhante) e definir

void A::print(FILE *f) {
    printto(ofstream(f));
}

string A::to_str() {
    ostringstream os;
    printto(os);
    return os.str();
}

Claro, você realmente deveria usar snprintf em vez de sprintf para evitar buffer overflows.Você também pode alterar seletivamente os sprintfs mais arriscados para o formato <<, para ser mais seguro e ainda alterar o mínimo possível.

Você deve tentar o arquivo de cabeçalho SafeFormat da biblioteca Loki (http://loki-lib.sourceforge.net/index.php?n=Idiomas.Printf).É semelhante à biblioteca de formato de string do boost, mas mantém a sintaxe das funções printf(...).

Eu espero que isso ajude!

Isso é sobre serialização?Ou impressão adequada?No primeiro caso, considere boost::serialization também.É tudo uma questão de serialização "recursiva" de objetos e subobjetos.

A biblioteca {fmt} fornece fmt::sprintf função que executa printf-formatação compatível (incluindo argumentos posicionais de acordo com Especificação POSIX) e retorna o resultado como std::string:

std::string s = fmt::sprintf("The answer is %d.", 42);

Isenção de responsabilidade:Eu sou o autor desta biblioteca.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top