# Erros esquisitos que nenhum principiante deveria enfrentar
Caloni, 2006-04-10 <computer> <veryold> <draft> [up] [copy]A tentativa de tornar C++ mais simples de usar para tarefas simples deu um grande avanço a partir do padrão de 1998. Veja esse exemplo que lê um inteiro na entrada e escreve ele novamente na saída na base hexadecimal:
#include <iostream>
using namespace std;
int main()
{
int num = 0;
cout << "Digite um numero. Qualquer numero! ";
if( cin >> num )
cout << "Esse numero em hexa fica " << hex << num << ", nao fica?\n";
else
cout << "Bad, bad number.\n";
return num;
}
Algumas entradas e saídas:
Digite um numero. Qualquer numero! 123456
Esse numero em hexa fica 1e240, nao fica?
Digite um numero. Qualquer numero! 42
Esse numero em hexa fica 2a, nao fica?
Digite um numero. Qualquer numero! babaca
Bad, bad number.
Intuitivo e direto. Porém, tem uma coisa que eu não gostei nele: não imprime as letras usadas no formato hexadecimal com caracteres em maiúsculo. Bom, isso não é - ou seria - um problema tão grande, já que a biblioteca de I/O da STL fornece inúmeras rotinas para formatar a saída como bem desejarmos. Para imprimir em maiúsculas, por exemplo, só precisaríamos mudar uma linha:
cout << "Esse numero em hexa fica " << hex << setiosflags(ios_base::uppercase) << num << ", nao fica?\n";
Tudo bem, aumenta o tamanho da linha. E também temos que usar o especificador de escopo (ios_base::), já que as definições dos nomes dos flags ficam dentro da classe base de todas as classes de I/O.
Mas esses não são os maiores problemas. Depois dessa singela modificação, o programa simplesmente não compila mais. E não é aquele errinho besta que aparece todo santo dia após algumas pequenas mudanças no código, um lugar comum facilmente detectável. É uma seqüência de erros que acaba ficando bem maior que o próprio programa!
error C2593: 'operator <<' is ambiguous
(...)\include\ostream(434): could be 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(std::basic_ostream<_Elem,_Traits>::_Mysb *)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\ostream(414): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(const void *)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(394): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(long double)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(374): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(double)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(354): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(float)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(333): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(unsigned __int64)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(313): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(__int64)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(292): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(unsigned long)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(272): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(long)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(252): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(unsigned int)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(227): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(int)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(207): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(unsigned short)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(174): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(short)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(154): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(std::_Bool)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(148): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(std::ios_base &(__cdecl *)(std::ios_base &))'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(142): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(std::basic_ostream<_Elem,_Traits>::_Myios
&(__cdecl *)(std::basic_ostream<_Elem,_Traits>::_Myios &))'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(137): or 'std::basic_ostream<_Elem,_Traits>::_Myt
&std;::basic_ostream<_Elem,_Traits>::operator <<(std::basic_ostream<_Elem,_Traits>::_Myt
&(__cdecl *)(std::basic_ostream<_Elem,_Traits>::_Myt &))'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(887): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,unsigned char)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(880): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const unsigned char *)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(873): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,signed char)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(866): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const signed char *)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(744): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,char)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(697): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(659): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,char)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
(...)\include\ostream(613): or 'std::basic_ostream<_Elem,_Traits>
&std;::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
while trying to match the argument list '(std::basic_ostream<_Elem,_Traits>::_Myt, 'unknown-type')'
with
[
_Elem=char,
_Traits=std::char_traits<char>
]
error C3861: 'setiosflags': identifier not found, even with argument-dependent lookup
Depois disso parece um tanto óbvio o porquê de tantas pessoas começarem a programar em C++ e pararem na mesma semana. Se o principiante não conhecer algum fórum onde possa pedir ajuda aos "gurus" da linguagem, dificilmente irá se livrar desse monte de erros sozinho. E o pior é que aqui no Brasil - seja original ou tradução - estamos cheios de livros com pequenos lapsos nos códigos. Tudo isso contribui para um razoável aumento na curva de aprendizado dessa linguagem comparada às outras.
A solução para o problema acima não é nada de arrancar os cabelos. O real problema é que o flag usado não está definido no único header que incluímos. Esses flags ficam em um lugar especial, um outro header chamado iomanip. Sabendo isso daí fica fácil corrigir:
#include <iomanip>
Esses detalhes acabam assustanto até pessoas mais experientes que desejam subir mais um degrau em seu aprendizado C++ e passar a usar a tão famosa STL e suas inúmeras vantagens. Esse pequeno exemplo demonstra que precisamos de uns retoques finais no que é - reconheço - uma das obras de arte da programação contemporânea: os conceitos e a produtividade que nos fornece a Standard Template Library.
A linguagem C++ me fascina, porém já comecei e parei com ela inúmeras vezes. Estes erros de compilação assustam mesmo, e acabam sempre me fazendo pensar que não tenho capacidade para trabalhar com a linguagem.
Agora me explica uma coisa: o compilador não poderia ser mais claro ao exibir o erro? Informar que o operador >> é ambíbuo não ajudou muito (pra não dizer nada) neste caso.
Porém, se quer um conselho, não tenha medo quando seu compilador te mostrar 514324 erros. Geralmente o primeiro e o segundo são os que importam. Também é útil se aproveitar do desenvolvimento incremental: faz algumas linhas, compila. Mais algumas linhas, compila de novo. E de novo. E de novo. Quando acontecer um erro, é mais fácil de localizar, pois estará relacionado às últimas modificações.
Já em erros como: std::list < int, int > l; (personalização incorreta do allocator) já sabemos que o gcc percorrerá alguns includes mas haverá uma linha de mensagem contendo "instantiated from here" que indica a linha original do erro no fonte. É só procurá-la.Isso torna-se um pouco mais confuso em qualquer 'Visual C++'.Mas na linha de comando melhora: cl -GX arq.cpp -o arq.exe; na segunda linha já temos o erro.Acho bom ter sempre à mão o gcc para dirimir dúvidas. E, em alguns casos mais "cabeludos" a ajuda será inestimável. Um exemplo típico no Visual C++ é o "Internal Error C1001". Bug e ainda ocorre(menos) no VC7 e no VC8.Uma solução imediata infalível em casos como esse é compilar com o gcc. E lá está o erro ou então não há erro e aí basta checar opções de compilação que também podem gerar o problema.
Fabricantes de compilador C++ sofrem mais; mas melhoram com o tempo. Em se tratando de compiladores, "maturidade" é uma palavra chave.
Ainda assim, o microsoft até hoje compila isso:std::list < int > ; // chamada ao construtor? devia explicitar... // microsoft/cl -> sem errosMas o gcc, corretamente, nos diz que: error: declaration does not declare anythingPor isso, também quanto aos padrões, é sempre bom dar uma "repassada" com o gcc.E, é lógico, nem há o que dizer se o alvo for Unix.
abraços,Basilio Miranda
PS: gostei de ter visitado este site, que só localizei recentemente. Disseminar a linguagem com bom nível é de grande importância neste nosso rincão natal - onde até "clipper" fez sucesso...
Mesmo assim, fica difícil para um iniciante configurar um compilador. Dois, então, já seria abuso =).
[]s
Além disso hoje podemos compilar mais do que apenas código 100% ISO, pois o gcc para Win32 já tem as bibliotecas necessárias para compilar aplicações Win32. O único problema é com a MFC, mas isso devido ao fato de que o gcc não irá perdoar coisas bem fora do padrão existentes dentro da MFC, como por exemplo "friend COleDateTime" (ao invés de "friend class ....") ou "for (int x ; ... ; ... )" que o compilador microsoft permite. Problema semelhante teremos com ATL.Mas creio que isso não se coloca para um iniciante, pois não creio que ele vá usar logo a MFC ou a ATL. Quando precisar de aplicações assim, terá que usar sim o VC pois trata-se de uma realidade inescapável no mercado. Apenas (e felizmente) esse não é o único mercado do programador C++... Assim talvez seja melhor aprender o C++ propriamente dito antes de ter que lidar com essas especificidades.
Além disso, se usar o VC++8 terá que se virar com o fato de que, por default(!) ele passou a considerar "deprecated" as funções para string(vetor char) da biblioteca padrão. Ou usamos as extensions do "Secure C" ou temos que anular explicitamente o default. Aliás, P. J. Plauger no Editor's Forum da "C++ Users Journal" de janeiro disse o que devia ser dito sobre esse "deprecated".Por isso acho o gcc mais saudável: sempre foi o compilador mais compatível com os padrões e até em mensagens de erro é mais esperto. Além disso é open-source e, secundariamente, free (o que não é tão pouco...). Não é que eu seja fundamentalista ou "xiita" em relação a C++ mas ultimamente ando meio cansado das "especificidades" microsoft...
Abraços,Basilio Miranda
É ótimo que o gcc tenha mensagens de erros mais claras. E, como você bem disse, MFC e ATL não são coisas para usar no começo. Mas isso não impede que o principiante use outros ambientes igualmente gratuitos, como o Visual C++ Express Edition [1] e o Borland C++ Command Line Tools [2], que oferecem o mesmo ambiente básico de programação C++. As extensões da Microsoft costumam existir para facilitar e agilizar o trabalho do programador. Isso para portabilidade é péssimo, mas se seu programa vai rodar só em Windows é uma mão na roda. E sempre existe a possibilidade do programa ser feito em camadas portáveis e não-portáveis.
Note que não estou fazendo apologia ao Visual C++, assim como você não fez com o GCC. Somente acredito que, como a maioria dos mortais utiliza Windows no seu dia a dia, é natural começar a programar em um ambiente que no futuro poderá oferecer as bibliotecas já mencionadas. Claro que, caso estejamos falando de um programador iniciante do mundo Linux, aí não tem conversa: GCC é o ambiente nativo e desejável. E de cara ele já ganha um ambiente mais de acordo com as regras do padrão C++ ISO. O que não é pouco =).
Aproveito a conversa sobre esse tema para indicar aos iniciantes alguns artigos bem interessantes e animadores, disponíveis no site 1bit: [3], [4] e [5].
[1] http://www.1bit.com.br/content.1bit/weblog/vc_express[2] http://www.borland.com/downloads/download_cbuilder.html[3] http://www.1bit.com.br/content.1bit/weblog/faq_cpp_start[4] http://www.1bit.com.br/content.1bit/programador[5] http://www.1bit.com.br/content.1bit/bom_programador
Esse assunto é importante para o programador e para o iniciante, até porque no Brasil é muito comum, no que diz respeito ao ambiente 'PC', misturar 3 coisas: linguagens / compiladores / IDE's. Pois, nas práticas predominantes em nosso país, imperam produtos (VB, etc) onde tudo parece ser a mesma coisa.Mas com C/C++ a primeira pergunta a fazer é: qual compilador vou usar? Eventualmente, uma segunda pergunta: qual IDE vou usar?
Quanto aos problemas do iniciante (bem reais e concretos com C e C++, certamente) não estou defendendo que ele se preocupe com questões de portabilidade desde o início. Apenas que se preocupe em aprender C++: e só existe um C++ que é o padrão (lógico que isso implica em portabilidade também mas portabilidade não é só isso).E o iniciante é um tanto maltratado com o Visual C++.Quando falei das "especificidades" estava me referindo não às "facilidades" voltadas para programação Windows, mas sim às quebras de padrão da linguagem, comuns no VC.Alguns exemplos já citados (acrescento aqui suas consequências):
E no VC++8 (2005) há um problema extremamente sério que já comentei: o "depracated" por default para a família de funções de string.O iniciante usa uma strcpy e toma uma "warning" que diz que isso é "depracated" - mas acontece que não é assim para a linguagem. E o compilador aconselha a usar uma extension microsoft. Isso é C++? O que estou aprendendo? C++ ou MS-C++? "Boas práticas" sobre buffers e strings serão aprendidas com o tempo - e há outras formas (padrão) de encaminhá-las. Mas essa warning não-padrão (e default!) só confunde o iniciante e irrita o experiente (ver artigo de Plauger no CUJ de janeiro).
Por isso acho que a primeira pergunta do iniciante: "qual compilador C/C++ usar?" - deveria ser respondida com "aquele que estiver mais coerente com o padrão linguagem"; ou seja, aquele em que você pode testar aquilo que "é" e aquilo que "não é" C++, para não acabar aprendendo gato por lebre.Quanto gente eu já vi defendendo que o "x" declarado em "for ( int x; " pode ser usado legitimamente fora desse escopo? Uma porção de gente oriunda de VC.Eis o problema. Num ambiente de treinamento, aí tudo bem, pois haverá um instrutor que pode esclarecer essas coisas e avisar das "irregularidades". E provavelmente outro compilador será pelo menos exibido. Mas para um cara sozinho isso é um problema.
Bem, é isso, desculpe mais uma vez a insistência mas penso que um iniciante deve ter consciência dessas coisas.
Abraços,Basilio Miranda
Esse assunto é importante para o programador e para o iniciante, até porque no Brasil é muito comum, no que diz respeito ao ambiente 'PC', misturar 3 coisas: linguagens / compiladores / IDE's (VB,etc). Mas com C/C++ a primeira pergunta a fazer é: qual compilador vou usar? Eventualmente, uma segunda pergunta: qual IDE vou usar?
Quanto aos problemas do iniciante não estou defendendo que ele se preocupe com questões de portabilidade desde o início. Apenas que se preocupe em aprender C++: e só existe um C++ que é o padrão (lógico que isso implica em portabilidade também, mas portabilidade não é só isso).E o iniciante é um tanto maltratado com o Visual C++.Quando falei das "especificidades" estava me referindo não às "facilidades" voltadas para programação Windows, mas sim às quebras de padrão da linguagem, comuns no VC.Alguns exemplos já citados (acrescento aqui suas consequências):
E no VC++8 (2005) há um problema extremamente sério que já comentei: o "depracated" por default para a família de funções de string.O iniciante usa uma strcpy e toma uma "warning" que diz que isso é "depracated" - mas acontece que não é assim para a linguagem. E o compilador aconselha a usar uma extension microsoft. Isso é C++? O que estou aprendendo? C++ ou MS-C++? "Boas práticas" sobre buffers e strings serão aprendidas com o tempo - e há outras formas (padrão) de encaminhá-las. Mas essa warning não-padrão (e default!) só confunde o iniciante e irrita o experiente (ver artigo de Plauger no CUJ de janeiro).
Por isso acho que a primeira pergunta do iniciante: "qual compilador C/C++ usar?" - deveria ser respondida com "aquele que estiver mais coerente com o padrão linguagem"; ou seja, aquele em que você pode testar aquilo que "é" e aquilo que "não é" C++, para não acabar aprendendo gato por lebre.Quanto gente eu já vi defendendo que o "x" declarado em "for ( int x; " pode ser usado legitimamente fora desse escopo? Uma porção de gente oriunda de VC.Eis o problema. Num ambiente de treinamento, aí tudo bem, pois haverá um instrutor que pode esclarecer essas coisas e avisar das "irregularidades". E provavelmente outro compilador será pelo menos exibido. Mas para um cara sozinho isso é um problema.
Bem, é isso, desculpe mais uma vez a insistência mas penso que um iniciante deve ter consciência dessas coisas.
Abraços,Basilio Miranda
std::cout << "Esse numero em hexa fica "<< std::hex<< std::uppercase<< num<< ", nao fica?\n";
mais simples né?
Sim, com certeza mais simples. Eu tentei exemplificar o uso de alguma função do header iomanip para forçar o erro em que o iniciante ficaria perdido. Talvez um setw(8) ou setf('0') fosse mais realista que o meu setiosflags =).
os motivos do meu comentário sobre os "pequenos retoques" certamente não ficaram tão claros como eu gostaria. Eu estava me referindo à alguns detalhes que tornariam a caminhada do iniciante com menos paradas até o ponto em que ele faz algo realmente útil. Alguns dos itens que poderiam ser melhorados:
Esses são os que me vêm à mente no momento, mas existem mais. Essas são mudanças na linguagem e que podem melhorar um pouco a vida do iniciante.
Os compiladores também podem melhorar, e muito. Até porque eles ainda estão ajustando a imensidão que se tornou o padrão C++ (tanto que, até onde sei, o único compilador que implementa 100% do padrão é o da Digital Mars, com seus export template).
Concordo que o lanco do deprecated é muito triste e totalmente lamentável. Quer mais? O ambiente do VC8 marca a palavra "array" como reservada (como int, class...). Talvez eu tenha desinstalado meu Visual 2003 cedo demais...
[]s