Dica do “Mestre dos Magos”, Scott Meyers: std::endl é ruim!

Se você já “futucou” C++, já leu algo a respeito dos objetos cin, cout, cerr e clog (é… existe um clog!) e já viu algo assim:

std::cout << "Hello, world!" << std::endl;

Muitos desenvolvedores novatos usam std::endl pensando que ele é equivalente ao caracter ‘\n’. Pasmem! Não é! O que std::endl faz é parecido com a equivalência ao caracter ‘\n’. Ele escreve o caracter e, logo em seguida, faz o flush do output stream.

Acontece que quanto se tem muitas linhas para escrever, é comum ver códigos assim:

std::cout << "Linha 1" << std::endl
          << "Linha 2" << std::endl
          << "linha 3" << std::end;

A cada vez que usamos o manipulador std::endl estamos criando um flush no stream de saída. Eis duas observações importantes:

  1. cout é, por tradição, bufferizado – mas a especificação de C++ não diz nada sobre isso. Segundo Scott Meyers, ele deveria ser unbuffered;
  2. cerr e clog (que normalmente é direcionado para o mesmo stream de cerr, ou seja, stderr) é, por definição, unbuffered. Ou seja, flushing não é necessário..

Outra coisa interessante é sobre o uso conjunto de cin e cout. Sempre que usa-se o objeto cin, o objeto cout é automaticamente “descarregado” (flushed).

Isso tudo ai em cima torna o uso do manipulador std::endl inapropriado, do ponto de vista da performance. Isso porque streamming é um método de aumentar a performance! Quando você escreve alguma coisa num arquivo (ofstream) ou em cout, se o fizer byte por byte, vai levar um tempo enorme porque é mais rápido escrever um conjunto de caracteres (bufferizados) do que cada um por vez. Daí, objetos derivados de ostream tendem a serem bufferizados. A mesma coisa acontece com as rotinas em stdio.h.

Ao usar a estrutura FILE e funções como fprintf, os caracteres são escritos primeiro em um buffer e, quando esse encher, o buffer é descarregado. Na especificação POSIX.1 stderr é descrito como unbuffered e stdout como line-buffered (dê também uma olhada em “man stdout”, no seu Linux, se tiver o pacote “manpages-dev” instalado). Ou seja, ao usar stderr você não precisa chamar fflush. E ao usar stdout, sempre que funções como fprintf encontrarem um ‘\n‘, haverá um fflush. Os mesmos princípios aplicam-se a cout, cerr e clog, em C++. Já em C, a única diferença é que é prudente chamar um fflush depois de um printf quando não há um ‘\n’ no final da string sempre que, logo depois, você for chamar um scanf. Não há um flush automático de stdout, como há em C++, neste caso.

Alguns desenvolvedores fazem chamadas a fflush usando o stream stdout para garantir que o buffer seja descarregado antes de usarem o stream stdin (especialmente em projetos escolares):

  char nome[100];

  printf("Entre com seu nome: "); /* printf usa stdout */
  fflush(stdout);
  scanf("%s", nome);              /* scanf usa stdin */
  printf("Seu nome é \"%s\"\n", nome);

Neste caso, se retirarmos a chamada a fflush, provavelmebnte o prompt “Entre com seu nome: ” não será visto antes da chamada a scanf(). Isso acontece porque o stream stdout só é descarregado em duas condições: Quando o buffer estiver cheio ou quando um caracter ‘\n’ for encontrado…

Agora vocẽ deve estar pensando: “Mas, já que ‘\n’ descarrega o buffer de stdout, isso significa que std:endl é exatamente um alias para o ‘\n’, não é?”. Não! O manipulador endl explicitamente chama fflush em algumas implementações da biblioteca padrão do C++. Assim, ao invés de usar std::endl, se quiser facilitar a digitação de seu código, crie uma constante:

const char NL = '\n';

std::cout << "Hello, World!" << NL;

A eficiência ainda fica prejudicada pelo uso supérfluo do operator<<, mas, provavelmente o compilador tomará conta disso…

PS: Eu recomendo mesmo os livros Effective C++ e Effective STL, de Scott Meyers. Mesmo o programador experiente tem muita coisa a aprender com esse material!

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s