Estou usando muito printf
para fins de rastreamento / log no meu código, descobri que é uma fonte de erro de programação. Sempre achei o operador de inserção ( <<
) uma coisa estranha, mas estou começando a pensar que, ao usá-lo, poderia evitar alguns desses erros.
Alguém já teve uma revelação semelhante ou estou apenas agarrando palha aqui?
Alguns tiram pontos
- Minha linha de pensamento atual é que a segurança de tipo supera qualquer benefício do uso de printf. O verdadeiro problema é a cadeia de formatação e o uso de funções variadas não seguras para tipos.
- Talvez eu não esteja usando
<<
as variantes do fluxo de saída stl, mas certamente analisarei o uso de um mecanismo de segurança de tipo muito semelhante. - Grande parte do rastreamento / registro é condicional, mas eu gostaria de sempre executar o código para não perder bugs nos testes apenas porque é um ramo raramente utilizado.
printf
no mundo C ++? Estou faltando alguma coisa aqui?printf
em C ++. (Se é uma boa ideia é outra questão.)printf
tem algumas vantagens; veja minha resposta.Respostas:
printf, especialmente nos casos em que você pode se preocupar com desempenho (como sprintf e fprintf), é um truque realmente estranho. Surpreende-me constantemente que as pessoas que usam C ++ devido a uma sobrecarga de desempenho minúscula relacionada a funções virtuais então defendam o io do C.
Sim, para descobrir o formato de nossa saída, algo que podemos saber 100% em tempo de compilação, vamos analisar uma string de formato em tempo de execução dentro de uma tabela de salto maciçamente estranha usando códigos de formato inescrutáveis!
É claro que esses códigos de formato não poderiam ser criados para corresponder aos tipos que representam, isso seria muito fácil ... e você será lembrado toda vez que você pesquisar se é% llg ou% lg que esse idioma (com forte tipificação) faz com que você descobrir tipos manualmente para imprimir / digitalizar algo, E foi projetado para processadores anteriores a 32 bits.
Admito que o manuseio do C ++ da largura e precisão do formato é volumoso e pode usar um pouco de açúcar sintático, mas isso não significa que você precise defender o bizarro hack que é o principal sistema io de C. O básico absoluto é bastante fácil em qualquer idioma (embora você provavelmente deva usar algo como uma função de erro / fluxo de erro personalizados para código de depuração de qualquer maneira), os casos moderados são semelhantes a regex em C (fácil de escrever, difícil de analisar / depurar) ) e os casos complexos impossíveis em C.
(Se você usar os contêineres padrão, escreva para si mesmo algumas sobrecargas << do operador de modelo rápido que permitem fazer coisas como
std::cout << my_list << "\n";
para depuração, onde my_list é do tipolist<vector<pair<int,string> > >
.)fonte
operator<<(ostream&, T)
chamando ... bemsprintf
! O desempenho desprintf
não é o ideal, mas, devido a isso, o desempenho dos iostreams geralmente é ainda pior.A mistura de saída no estilo C
printf()
(ouputs()
ouputchar()
ou ...) com saída no estilo C ++std::cout << ...
pode não ser segura. Se bem me lembro, eles podem ter mecanismos de buffer separados, portanto a saída pode não aparecer na ordem desejada. (Como o AProgrammer menciona em um comentário,sync_with_stdio
trata disso).printf()
é fundamentalmente inseguro. O tipo esperado para um argumento é determinado pela sequência de caracteres de formato ("%d"
requer umint
ou algo que promovaint
,"%s"
requer umchar*
que deve apontar para uma sequência de estilo C terminada corretamente etc.), mas passar o tipo errado de argumento resulta em comportamento indefinido , não é um erro diagnosticável. Alguns compiladores, como o gcc, fazem um trabalho razoavelmente bom de aviso sobre incompatibilidades de tipo, mas podem fazê-lo apenas se a sequência de formatação for literal ou for conhecida em tempo de compilação (que é o caso mais comum) - e avisos não são exigidos pelo idioma. Se você passar o tipo errado de argumento, coisas arbitrariamente ruins podem acontecer.A E / S de fluxo do C ++, por outro lado, é muito mais segura quanto ao tipo, pois o
<<
operador está sobrecarregado para muitos tipos diferentes.std::cout << x
não precisa especificar o tipo dex
; o compilador irá gerar o código certo para qualquer tipox
.Por outro lado,
printf
as opções de formatação são muito mais convenientes. Se eu quiser imprimir um valor de ponto flutuante com 3 dígitos após o ponto decimal, posso usar"%.3f"
- e isso não afeta outros argumentos, mesmo dentro da mesmaprintf
chamada. Os C ++setprecision
, por outro lado, afetam o estado do fluxo e podem atrapalhar a saída posterior, se você não tomar muito cuidado para restaurar o fluxo ao estado anterior. (Esta é minha irritação pessoal; se estiver faltando alguma maneira limpa de evitá-la, comente.)Ambos têm vantagens e desvantagens. A disponibilidade de
printf
é particularmente útil se você tiver um plano de fundo em C e estiver mais familiarizado com ele, ou se estiver importando o código-fonte em um programa em C ++.std::cout << ...
é mais idiomático para C ++ e não requer muito cuidado para evitar incompatibilidades de tipo. Ambos são C ++ válidos (o padrão C ++ inclui a maior parte da biblioteca padrão C por referência).É provavelmente melhor usar
std::cout << ...
para o bem de outros programadores C ++ que podem trabalhar em seu código, mas você pode usar qualquer um - especialmente no código de rastreamento que você vai jogar fora.E é claro que vale a pena gastar algum tempo aprendendo a usar depuradores (mas isso pode não ser viável em alguns ambientes).
fonte
printf
.std::ios_base::sync_with_stdio
.std::cout
usa uma chamada separada para cada item que é impresso? Você pode contornar isso coletando uma linha de saída em uma string antes de imprimi-la. E é claro que você também pode imprimir um item de cada vezprintf
; é apenas mais conveniente imprimir uma linha (ou mais) em uma chamada.É provável que seu problema venha da mistura de dois gerenciadores de saída padrão muito diferentes, cada um dos quais com sua própria agenda para o pobre e pequeno STDOUT. Você não recebe garantias sobre como elas são implementadas, e é perfeitamente possível que elas definam opções conflitantes de descritores de arquivos, ambas tentem fazer coisas diferentes, etc. Além disso, os operadores de inserção têm um grande problema
printf
:printf
permitem fazer isso:Considerando
<<
que não.Nota: Para depuração, você não usa
printf
oucout
. Você usafprintf(stderr, ...)
ecerr
.fonte
printf
não é seguro para o tipo e eu estou pensando atualmente que esse tipo de segurança supera qualquer benefício do usoprintf
. O problema é realmente a string de formato e a função variadica não segura para tipos.SomeObject
for um ponteiro? Você receberá dados binários arbitrários que o compilador decide representarSomeObject
.Existem muitos grupos - por exemplo, o google - que não gostam de fluxos.
http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams
(Abra o triângulo para que você possa ver a discussão.) Acho que o guia de estilo do Google C ++ tem MUITO conselho sensato.
Penso que a desvantagem é que os fluxos são mais seguros, mas o printf é mais claro de ler (e mais fácil de obter exatamente a formatação desejada).
fonte
printf
pode causar erros devido à falta de segurança do tipo. Existem algumas maneiras de resolver isso sem mudar paraiostream
o<<
operador e formatação mais complicada:printf
seqüências de caracteres de formato em relação aosprintf
argumentos e podem exibir avisos como os seguintes, se não corresponderem.printf
chamadas -style para torná-las seguras.printf
cadeias de formato semelhantes (em particular, as Boost.Format são quase idênticas aprintf
), mantendoiostreams
'segurança de tipo e extensibilidade de tipo.fonte
A sintaxe Printf é basicamente boa, menos algumas das digitações obscuras. Se você acha errado, por que C #, Python e outras linguagens usam a construção muito semelhante? O problema em C ou C ++: não faz parte de um idioma e, portanto, não é verificado pelo compilador quanto à sintaxe correta (*) e não é decomposto em uma série de chamadas nativas se estiver otimizando a velocidade. Observe que, se otimizar o tamanho, as chamadas printf podem se tornar mais eficientes! A sintaxe do streaming em C ++ não é nada boa. Funciona, a segurança de tipo está lá, mas a sintaxe detalhada ... bleh. Quero dizer, eu uso, mas sem alegria.
(*) alguns compiladores fazem essa verificação e quase todas as ferramentas de análise estática (eu uso o Lint e nunca tive problemas com o printf desde então).
fonte
format("fmt") % arg1 % arg2 ...;
) com a segurança de tipo. Ao custo de um pouco mais de desempenho, porque gera chamadas sequenciais que geram internamente chamadas sprintf em muitas implementações.printf
é, na minha opinião, uma ferramenta de saída muito mais flexível para lidar com variáveis do que qualquer saída de fluxo CPP. Por exemplo:No entanto, onde você pode querer usar o
<<
operador CPP é quando você o sobrecarrega para um método específico ... por exemplo, para obter um despejo de um objeto que contém os dados de uma pessoa em particular,PersonData
....Para isso, seria muito mais eficaz dizer (supondo que
a
seja um objeto de PersonData)do que:
O primeiro está muito mais alinhado com o princípio do encapsulamento (não há necessidade de conhecer as especificidades, variáveis de membros particulares) e também é mais fácil de ler.
fonte
Você não deveria usar
printf
no C ++. Sempre. O motivo é, como você observou corretamente, que é uma fonte de bugs e o fato de imprimir tipos personalizados e em C ++ quase tudo deve ser de tipos personalizados é um problema. A solução C ++ é os fluxos.No entanto, existe um problema crítico que torna os fluxos inadequados para qualquer saída visível para o usuário! O problema são as traduções. O exemplo de empréstimo do manual gettext diz que você deseja escrever:
Agora o tradutor alemão aparece e diz: Ok, em alemão, a mensagem deve ser
E agora você está com problemas, porque ele precisa que as peças sejam embaralhadas. Deve-se dizer que mesmo muitas implementações
printf
têm problemas com isso. A menos que eles suportem a extensão para que você possa usarO Boost.Format suporta formatos no estilo printf e possui esse recurso. Então você escreve:
Infelizmente, isso acarreta um pouco de penalidade no desempenho, porque internamente cria uma cadeia de caracteres e usa o
<<
operador para formatar cada bit e, em muitas implementações, o<<
operador chama internamentesprintf
. Eu suspeito que uma implementação mais eficiente seria possível se realmente desejado.fonte
Você está fazendo muito trabalho inútil, além do
stl
mal ou não, depure seu código com uma série deprintf
apenas mais 1 nível possível de falhas.Basta usar um depurador e ler algo sobre exceções e como capturá-las e lançá-las; tente não ser mais detalhado do que realmente precisa.
PS
printf
é usado em C, para o C ++ você temstd::cout
fonte