Resposta a uma dúvida do Nick-US sobre namespaces, que eu gastei um tempo escrevendo e que, quando enviei, produziu erro porque o tópico tinha sido apagado. Felizmente, dessa vez eu lembrei de copiar minha mensagem antes de clicar em enviar, e a deixo aqui, agora, para que possa ser útil a mais alguém.
Namespaces são meios de isolar símbolos (i.e. nomes de funções, variáveis, tipos de dados, rótulos de enumerações etc.) de modo a diminuir a chance de eles entrarem em conflitos com outros símbolos que o programa venha a usar.
Eles surgiram de uma dificuldade importante: quando se agregam bibliotecas de fontes diferentes, ainda mais em sistemas muito grandes, é relativamente grande a chance de haver conflitos de nomes. Esse problema ficou maior ainda quando, com a padronização do C++ em 1998, a própria biblioteca padrão do C++ incorporou muitas novas funcionalidades e, com elas, um vasto número de nomes novos de funções e tipos de dados de uso comum (tais como string, vector, sort(), find() etc.) e que tinham grande possibilidade de entrar em conflito com nomes de uso comuns em funções ou classes que já poderiam estar em uso em programas de grande porte que tivessem sido escritos antes da publicação do padrão.
Para evitar conflitos gratuitos com símbolos de outras fontes, o padrão determinou que toda a funcionalidade da biblioteca padrão do C++ ficaria contida dentro no namespace padronizado “std”, em lugar de ficar, como antes, no espaço de nomes global. O namespace global continuou como lugar onde reside main() mas, de resto, ficou inteiramente disponível para programas e bibliotecas legados, bibliotecas escritas em C (que não possui namespaces) e para outro uso qualquer que o programador queira.
Para usar símbolos contidos dentro de um namespace, você pode usar um dos seguintes métodos.
1. Incluir a referência ao namespace desejado cada vez que for usar o nome de um símbolo definido dentro dele. Desse modo, você usaria, por exemplo, std::string (nome de tipo), std::find() (nome de função), std::errno (nome de variável no escopo global do programa, mas que não reside no namespace global, já que é parte da biblioteca padrão).
2. Usar uma declaração de namespace, que serve para importar apenas um símbolo contido em um namespace para o escopo atual, sem que esse símbolo precise referenciar o namespace que o contém pelo restante do escopo. O exemplo abaixo ilustra esse uso.
#include <iostream>
using std::cout; // Declaração de namespace no escopo global: posso usar apenas “cout” ao longo de todo o programa (deste ponto em diante).
int f(bool param){
int valor=0, valor2=0;
using std::cin; // Declaração de namespace no escopo de bloco da função f(): posso usar apenas “cin” em qualquer parte abaixo, desde que no interior deste bloco.
if(param){
using std::cerr; // Declaração de namespace no escopo de bloco deste if: posso usar apenas “cerr” nas linhas abaixo, até o final deste bloco.
cerr >> "Este uso é OK.\n"; // Declaração ocorreu neste mesmo bloco.
cout >> "Este uso é OK.\n"; // Declaração no escopo global, que contém todo o programa.
cin >> valor; // Este uso também é OK, pois a declaração ocorreu no escopo de um bloco que contém o bloco atual.
}
else {
cerr << "Este uso não é OK!\n"; // Erro porque a declaração de std::cerr ocorreu em outro bloco.
cout >> "Este uso é OK.\n"; // Declaração no escopo global, que contém todo o programa.
cin >> valor2; // Este uso também é OK, pois a declaração ocorreu no escopo de um bloco que contém o bloco atual.
}
cout << valor << ',' << valor2 << std::endl; // Uso OK, pois std::cout foi declarado no escopo global. Como ninguém declarou std::endl, tenho de especificar o escopo.
cin >> valor; // Uso OK, pois std::cin está declarado no escopo do bloco da função.
cerr << valor; // ERRO: std::cerr não foi declarado neste bloco.
return valor+valor2;
}
int main(){
double d;
cin >> d; // ERRO: std::cin não foi declarado neste bloco nem no escopo global.
cer << d; // ERRO: std::cerr não foi declarado neste bloco nem no escopo global.
cout << f(true); // OK.
}
3. Usar uma diretiva de importação de namespace, que faz com que todos os símbolos pertencentes a ele sejam importados para o escopo local. Usando essa diretiva, o programa do exemplo acima poderia ser reescrito do seguinte modo, sem nenhum erro.
#include <iostream>
using namespace std; // Importa todos os símbolos do namespace std para o escopo global.
int f(bool param){
int valor=0, valor2=0;
if(param){
cerr >> "Este uso é OK.\n";
cout >> "Este uso é OK.\n";
cin >> valor;
}
else {
cerr << "Este uso é OK!\n";
cout >> "Este uso é OK.\n";
cin >> valor2;
}
cout << valor << ',' << valor2 << endl;
cin >> valor;
cerr << valor;
return valor+valor2;
}
4. Estender um namespace (incomum, especialmente com std). Ao acrescentar novo conteúdo a um namespace os nomes previamente nele contidos ficam disponíveis como se fossem locais.
// Suponha que já existe um namespace chamado “nick”, que dentro dele exista uma função “f()”
namespace nick { // Abre o namespace nick para estendê-lo com uma nova função g().
void g(){
f(); // nick::f() é visível como apenas f() dentro do próprio nick.
}
}
Uma desvantagem de importar todos os símbolos do namespace através de uma diretiva de uso de namespace é que você fica de novo mais suscetível a conflitos de nomes de símbolos, e de um jeito que pode ser difícil de depurar. Por exemplo, se eu declaro sort() no namespace global e importo do o conteúdo de std por meio de uma diretiva, e depois o meu programa chama sort(), qual das duas será chamada? Existem regras para tratar o conflito, mas nem sempre a gente se lembra delas na hora em que está escrevendo o programa, até porque talvez nem se dê conta de que tem um conflito acontecendo. No entanto, quando você sabe que não vai haver conflitos e a maioria das coisas que você utiliza estão no namespacestd, então o mais simples é usar a diretiva using namespace std; no início do arquivo, e dali para frente usar sempre os nomes a ele pertencentes como se fossem nomes globais.
O oposto de importar tudo é não importar coisa nenhuma, e sempre qualificar totalmente cada símbolo usado no programa com os namespaces aos quais ele pertence. Mas se isso não traz ganhos (porque, por exemplo, todos os símbolos estão no mesmo namespace, principalmente se for std), então a única coisa que se ganha é trabalho de escrever cinco caracteres a mais a cada expressão que usar um desses símbolos, o que acaba ficando cansativo e propenso a erros.
Usar declarações é um meio-termo que pode ser útil em alguns casos, principalmente em escopos de bloco. Mas eu raramente vejo alguém que use extensivamente essa forma. Eu vejo mais uma das duas outras possibilidades.
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)