C++ Namespaces
Desde a primeira aula você escreve std::cout, std::string, std::vector. Esse std:: na frente é um namespace, um "sobrenome" que separa os nomes da biblioteca padrão dos seus. Quando um programa cresce, e principalmente quando junta código de várias pessoas e bibliotecas, dois pedaços acabam querendo o mesmo nome: duas funções preco, duas imprimir. Nesta aula você vai criar os seus próprios namespaces, entender o famoso using namespace std; e ver o erro real que ele causa.
- Por que dois nomes iguais dão erro, e como um namespace resolve isso.
- Como criar
namespace loja { … }e usarloja::preco. - O que o
std::significa. - As três formas de encurtar: o nome completo,
using loja::preco;eusing namespace loja;. - O problema do
using namespace std;: um nome seu colidindo comstd::countoustd::distance. - Namespaces dentro de namespaces (
loja::caixa), apelidos e o namespace anônimo.
Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: nome e sobrenome
Na turma da Ana há duas Marias. Quando a professora chama "Maria!", as duas olham. A solução é usar o sobrenome: "Maria Silva" e "Maria Souza". Ou pense em endereços: existe uma "Rua das Flores" em centenas de cidades, e ninguém se confunde, porque o endereço completo inclui a cidade.
| No dia a dia | No C++ |
|---|---|
| O sobrenome, a cidade | o namespace (em inglês, "espaço de nomes") |
| "Maria Silva", "Rua das Flores, Campinas" | loja::preco, std::cout |
| Dentro de casa, todo mundo diz só "Maria" | dentro do namespace, basta o nome curto |
1. O problema: dois nomes iguais
A Ana vende na lojinha e também faz as contas da padaria do bairro. Cada uma tem sua tabela de preços, e as duas funções se chamam preco:
#include <iostream>
double preco(int codigo) { return codigo * 1.5; } // da lojinha
double preco(int codigo) { return codigo * 2.0; } // da padaria
int main() {
std::cout << preco(10) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra conflito.cpp -o conflito
conflito.cpp:4:8: error: redefinition of ‘double preco(int)’
4 | double preco(int codigo) { return codigo * 2.0; } // da padaria
| ^~~~~
conflito.cpp:3:8: note: ‘double preco(int)’ previously defined here
3 | double preco(int codigo) { return codigo * 1.5; } // da lojinha
| ^~~~~
"Redefinição de double preco(int)": a nota mostra onde estava a primeira. Duas funções com o mesmo nome e os mesmos parâmetros não podem conviver no mesmo lugar (com parâmetros diferentes até poderiam: é a sobrecarga). Renomear para precoLoja e precoPadaria funciona aqui, mas não resolve o caso geral: quando o conflito é com uma biblioteca de outra pessoa, você não pode renomear o código dela.
2. Criando um namespace
#include <iostream>
namespace loja {
double preco(int codigo) { return codigo * 1.5; }
}
namespace padaria {
double preco(int codigo) { return codigo * 2.0; }
}
int main() {
std::cout << "Loja: " << loja::preco(10) << '\n';
std::cout << "Padaria: " << padaria::preco(10) << '\n';
}
- namespace loja
- Cria (ou reabre) um namespace chamado
loja. O nome segue as regras de nomes de variáveis; o costume é tudo em minúsculas. - { … }
- Tudo o que for declarado entre as chaves ganha o "sobrenome"
loja. Não vai;depois da chave que fecha. - loja::preco(10)
- Chama a
precodo namespaceloja. O::se chama operador de escopo: leia como "oprecoque mora emloja".
$ g++ -std=c++23 -Wall -Wextra loja.cpp -o loja && ./loja Loja: 15 Padaria: 20
Agora as duas funções existem lado a lado: o nome completo de uma é loja::preco e o da outra é padaria::preco. Um namespace pode guardar qualquer coisa que tenha nome: funções, variáveis, constantes, structs, classes, outros namespaces.
Depois de pôr a função dentro do namespace, a Ana esqueceu de mudar a chamada:
#include <iostream>
namespace loja {
double preco(int codigo) { return codigo * 1.5; }
}
int main() {
std::cout << preco(10) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra sem.cpp -o sem
sem.cpp: In function ‘int main()’:
sem.cpp:8:18: error: ‘preco’ was not declared in this scope; did you mean ‘loja::preco’?
8 | std::cout << preco(10) << '\n';
| ^~~~~
| loja::preco
sem.cpp:4:12: note: ‘loja::preco’ declared here
4 | double preco(int codigo) { return codigo * 1.5; }
| ^~~~~
O que significa: fora do namespace, não existe nenhuma preco "solta". O g++ é gentil: pergunta "você quis dizer loja::preco?", mostra a correção embaixo do código e aponta onde ela foi declarada.
Como resolver: use o nome completo, loja::preco(10), ou um dos atalhos da seção 4.
Experimente: rode. Depois acrescente ao namespace padaria uma constante constexpr double TAXA = 0.05; e mostre padaria::TAXA no main. Por último, tente chamar preco(10) sem sobrenome e leia a mensagem.
#include <iostream>
namespace loja {
double preco(int codigo) { return codigo * 1.5; }
}
namespace padaria {
double preco(int codigo) { return codigo * 2.0; }
}
int main() {
std::cout << "Loja: " << loja::preco(10) << '\n';
std::cout << "Padaria: " << padaria::preco(10) << '\n';
}
3. O std é um namespace
Agora o std::cout faz sentido: cout mora no namespace std (de standard, "padrão"), onde fica toda a biblioteca padrão do C++: std::string, std::vector, std::sort, std::count… Sem o std::, o compilador não acha:
#include <iostream>
int main() {
cout << "Olá!\n";
}
$ g++ -std=c++23 -Wall -Wextra semstd.cpp -o semstd
semstd.cpp: In function ‘int main()’:
semstd.cpp:4:5: error: ‘cout’ was not declared in this scope; did you mean ‘std::cout’?
4 | cout << "Olá!\n";
| ^~~~
| std::cout
In file included from semstd.cpp:1:
/usr/include/c++/15/iostream:65:18: note: ‘std::cout’ declared here
65 | extern ostream cout; ///< Linked to standard output
| ^~~~
A nota mostra até a linha do arquivo iostream onde o std::cout é declarado. A biblioteca padrão tem centenas de nomes, muitos deles palavras comuns em inglês (count, "contar"; distance, "distância"; size, "tamanho"; max, "máximo"). Guardá-los todos no std deixa os nomes "soltos" livres para você.
4. Três formas de chamar: qual usar
Escrever loja:: toda vez pode cansar. O C++ tem dois atalhos com a palavra using ("usando"):
| Forma | Exemplo | O que faz | Risco |
|---|---|---|---|
| Nome completo | loja::preco(10) | diz exatamente qual | nenhum; só é mais comprido |
| Trazer um nome (using-declaration) | using loja::preco;depois: preco(10) | daqui em diante, preco quer dizer loja::preco | pequeno: você escolheu o nome, sabe que ele está ali |
| Trazer todos os nomes (using-directive) | using namespace loja;depois: preco(10) | todo nome de loja pode ser usado sem sobrenome | grande: traz nomes que você nem sabe que existem |
Veja o risco do using namespace com os dois namespaces da Ana:
#include <iostream>
namespace loja { double preco(int codigo) { return codigo * 1.5; } }
namespace padaria { double preco(int codigo) { return codigo * 2.0; } }
using namespace loja;
using namespace padaria;
int main() {
std::cout << preco(10) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra dois.cpp -o dois
dois.cpp: In function ‘int main()’:
dois.cpp:10:23: error: call of overloaded ‘preco(int)’ is ambiguous
10 | std::cout << preco(10) << '\n';
| ~~~~~^~~~
dois.cpp:10:23: note: there are 2 candidates
dois.cpp:3:28: note: candidate 1: ‘double loja::preco(int)’
3 | namespace loja { double preco(int codigo) { return codigo * 1.5; } }
| ^~~~~
dois.cpp:4:28: note: candidate 2: ‘double padaria::preco(int)’
4 | namespace padaria { double preco(int codigo) { return codigo * 2.0; } }
| ^~~~~
"A chamada de preco(int) é ambígua": há dois candidatos (candidates) que servem igualmente bem, e o compilador se recusa a adivinhar. Os using namespace derrubaram as paredes que os namespaces tinham levantado. A saída é voltar ao nome completo (loja::preco(10)).
5. using namespace std;
Em muitos tutoriais e livros você vai ver, logo depois dos #include:
using namespace std;
Ela traz todos os nomes da biblioteca padrão para o seu arquivo, e aí dá para escrever cout, string e vector sem std::. Parece ótimo, até o dia em que um nome seu coincide com um dos centenas de nomes do std. A Ana guardou quantos itens há no carrinho numa variável chamada count ("contagem"):
#include <iostream>
using namespace std;
int count = 3; // quantos itens há no carrinho
int main() {
cout << "Itens: " << count << '\n';
}
$ g++ -std=c++23 -Wall -Wextra count.cpp -o count
count.cpp: In function ‘int main()’:
count.cpp:7:26: error: reference to ‘count’ is ambiguous
7 | cout << "Itens: " << count << '\n';
| ^~~~~
In file included from /usr/include/c++/15/bits/unicode.h:39,
from /usr/include/c++/15/format:61,
from /usr/include/c++/15/ostream:44,
from /usr/include/c++/15/iostream:43,
from count.cpp:1:
/usr/include/c++/15/bits/stl_algo.h:4070:5: note: candidates are: ‘template<class _IIter, class _Tp> constexpr typename std::iterator_traits<_Iterator>::difference_type std::count(_IIter, _IIter, const _Tp&)’
4070 | count(_InputIterator __first, _InputIterator __last, const _Tp& __value)
| ^~~~~
count.cpp:4:5: note: ‘int count’
4 | int count = 3; // quantos itens há no carrinho
| ^~~~~
Assusta, mas dá para ler com calma:
- error: reference to ‘count’ is ambiguous
- "A referência a
counté ambígua": existem doiscountvisíveis e o compilador não sabe qual você quer. - In file included from … iostream:43, from count.cpp:1
- O caminho da colagem: o
<iostream>inclui o<ostream>, que inclui o<format>… e lá no fundo vem ostl_algo.h. A Ana nem incluiu<algorithm>, e mesmo assim ostd::countchegou. - note: candidates are: ‘template<…> … std::count(_IIter, _IIter, const _Tp&)’
- Candidato 1: a função
std::countda biblioteca padrão, que conta quantas vezes um valor aparece numa lista (você vai vê-la em Algoritmos). A parte comtemplateé o jeito técnico de escrever o tipo dela; não precisa entender agora. - note: ‘int count’
- Candidato 2: a variável da Ana, na linha 4.
Sem o using namespace std;, não haveria problema: o da biblioteca se chama std::count e o da Ana, count. O mesmo acontece com distance, outra palavra comum:
$ g++ -std=c++23 -Wall -Wextra distancia.cpp -o distancia
distancia.cpp: In function ‘int main()’:
distancia.cpp:7:30: error: reference to ‘distance’ is ambiguous
7 | cout << "Distância: " << distance << " km\n";
| ^~~~~~~~
In file included from /usr/include/c++/15/bits/stl_construct.h:62,
from /usr/include/c++/15/bits/char_traits.h:59,
from /usr/include/c++/15/ios:44,
from /usr/include/c++/15/bits/ostream.h:43,
from /usr/include/c++/15/ostream:42,
from /usr/include/c++/15/iostream:43,
from distancia.cpp:1:
/usr/include/c++/15/bits/stl_iterator_base_funcs.h:150:5: note: candidates are: ‘template<class _InputIterator> constexpr typename std::iterator_traits<_Iterator>::difference_type std::distance(_InputIterator, _InputIterator)’
150 | distance(_InputIterator __first, _InputIterator __last)
| ^~~~~~~~
distancia.cpp:4:5: note: ‘int distance’
4 | int distance = 12; // km até a padaria
| ^~~~~~~~
(O distancia.cpp é igual ao count.cpp, com int distance = 12; // km até a padaria na linha 4.) O pior desse problema é que ele pode aparecer sozinho: um programa que compilava hoje pode parar de compilar quando você atualizar o compilador e a biblioteca padrão ganhar um nome novo que coincide com um seu.
O conserto: traga só o que você usa
#include <iostream>
using std::cout;
int count = 3; // quantos itens há no carrinho
int main() {
cout << "Itens: " << count << '\n';
}
$ g++ -std=c++23 -Wall -Wextra count2.cpp -o count2 && ./count2 Itens: 3
using std::cout; traz só o cout. O std::count continua guardado no std, sem atrapalhar.
- Escreva
std::por extenso. São cinco letras, e quem lê sabe na hora de onde vem cada nome. - Se quiser encurtar num
.cpp, prefirausing std::cout;(um nome por vez), depois dos#include. using namespace std;é aceitável em programas pequenos de estudo, mas nunca num arquivo.h: o cabeçalho é colado em todos os arquivos que o incluem (veja Arquivos .h e .cpp), e ousingiria junto, sem que eles pedissem.- Um
usingtambém pode ficar dentro de uma função; aí ele vale só entre as chaves dela.
Missão: este programa não compila por causa do using namespace std;. Rode e leia o ambiguous. Conserte sem renomear a variável: troque o using namespace std; por using só dos nomes que o programa usa (ou use std:: por extenso). A saída deve ser Itens: 3.
#include <iostream>
#include <algorithm>
using namespace std;
int count = 3; // quantos itens há no carrinho
int main() {
cout << "Itens: " << count << '\n';
}
#include <iostream>
#include <algorithm>
using std::cout;
int count = 3; // quantos itens há no carrinho
int main() {
cout << "Itens: " << count << '\n';
}
6. Namespaces dentro de namespaces
Um namespace pode ter outros dentro. Projetos grandes usam isso para organizar as partes: loja::caixa, loja::estoque.
#include <iostream>
namespace loja {
namespace caixa {
double troco(double pago, double total) { return pago - total; }
}
}
namespace loja::estoque {
int minimo = 5;
}
int main() {
std::cout << "Troco: " << loja::caixa::troco(50, 32.5) << '\n';
std::cout << "Estoque mínimo: " << loja::estoque::minimo << '\n';
namespace lc = loja::caixa;
std::cout << "Troco: " << lc::troco(20, 7) << '\n';
}
- namespace loja { namespace caixa { … } }
- O jeito clássico: um dentro do outro. O nome completo da função é
loja::caixa::troco. - namespace loja::estoque { … }
- O jeito curto, com os dois nomes de uma vez C++17. Faz o mesmo que escrever os dois
namespaceaninhados. - namespace lc = loja::caixa;
- Um apelido (alias): daqui em diante,
lc::quer dizerloja::caixa::. Útil quando o nome é comprido, como onamespace fs = std::filesystem;que você vai ver em muito código.
$ g++ -std=c++23 -Wall -Wextra aninhado.cpp -o aninhado && ./aninhado Troco: 17.5 Estoque mínimo: 5 Troco: 13
Repare que o namespace loja foi aberto duas vezes no mesmo arquivo, e tudo bem: um namespace pode ser reaberto quantas vezes for preciso, inclusive em arquivos diferentes. É assim que a biblioteca padrão põe coisas no std a partir de dezenas de cabeçalhos.
O namespace global e o :: sozinho
O que não está em namespace nenhum mora no namespace global. Para pedir explicitamente o de lá, use :: sem nada antes:
#include <iostream>
double preco(int codigo) { return codigo * 1.0; }
namespace loja {
double preco(int codigo) { return codigo * 1.5; }
void mostrar() {
std::cout << "preco(10) = " << preco(10) << '\n';
std::cout << "::preco(10) = " << ::preco(10) << '\n';
}
}
int main() {
loja::mostrar();
}
$ g++ -std=c++23 -Wall -Wextra global.cpp -o global && ./global preco(10) = 15 ::preco(10) = 10
Dentro do namespace loja, o nome curto preco acha primeiro o da própria loja (como "Maria", dito dentro de casa, é a Maria da família). O ::preco vai direto ao global.
7. Namespaces com .h e .cpp
Num projeto dividido em arquivos, o cabeçalho declara dentro do namespace e o .cpp reabre o mesmo namespace para definir:
#pragma once
namespace loja {
double calcularTotal(double preco, int quantidade);
}
#include "calculos.h"
namespace loja {
double calcularTotal(double preco, int quantidade) {
return preco * quantidade;
}
}
Quem inclui o calculos.h chama loja::calcularTotal(2.50, 4). Você vai montar exatamente isso na missão final.
8. O namespace anônimo: "só deste arquivo"
Às vezes uma função é só uma ajudante, usada dentro de um .cpp. A Ana tem uma linha() que desenha um traço no main.cpp e outra linha(), diferente, no relatorio.cpp. Cada arquivo compila bem sozinho, mas na ligação:
$ g++ -std=c++23 -Wall -Wextra main.cpp relatorio.cpp -o loja /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccOHoCsw.o: na função "linha()": relatorio.cpp:(.text+0x0): múltiplas definições de "linha()"; /tmp/cc1uZyIO.o:main.cpp:(.text+0x0): definido primeiro aqui collect2: error: ld returned 1 exit status
É o erro do ligador que você conheceu em Compilar Vários Arquivos: o programa inteiro só pode ter uma linha(). A solução é dizer que cada linha() é particular do seu arquivo, com um namespace sem nome:
#include <iostream>
namespace {
void linha() { std::cout << "----------\n"; }
}
void relatorio();
int main() {
linha();
relatorio();
linha();
}
#include <iostream>
namespace {
void linha() { std::cout << "==========\n"; }
}
void relatorio() {
linha();
std::cout << "Relatório do dia\n";
linha();
}
$ g++ -std=c++23 -Wall -Wextra main.cpp relatorio.cpp -o loja && ./loja ---------- ========== Relatório do dia ========== ----------
- namespace { … }
- Namespace anônimo (sem nome). Dentro do próprio arquivo, os nomes são usados normalmente, sem prefixo. Para os outros arquivos, eles são invisíveis.
- o resultado
- Cada
.cpptem a sualinha()particular, e o ligador não vê conflito: omainusou o traço-e o relatório usou o=.
O nm mostra a diferença: a letra é t minúscula, que quer dizer "definida aqui, mas só para uso local".
$ g++ -std=c++23 -Wall -Wextra -c main.cpp && nm -C main.o | grep linha 0000000000000000 t (anonymous namespace)::linha()
Em código antigo, o mesmo efeito é obtido escrevendo static antes da função (static void linha() { … }). Em C++ moderno, prefira o namespace anônimo, que vale também para structs e classes. Use-o para toda ajudante que não aparece no .h, e nunca dentro de um .h (cada arquivo que o incluísse ganharia uma cópia particular).
Referência
| Escrita | O que faz |
|---|---|
namespace loja { … } | cria ou reabre o namespace loja |
loja::preco | o preco de dentro da loja |
::preco | o preco do namespace global |
using loja::preco; | traz um nome |
using namespace loja; | traz todos os nomes (evite com std e nos .h) |
namespace loja { namespace caixa { … } } | namespace aninhado |
namespace loja::caixa { … } | o mesmo, forma curta C++17 |
namespace lc = loja::caixa; | apelido |
namespace { … } | anônimo: só deste .cpp |
| Mensagem | Causa provável |
|---|---|
‘x’ was not declared in this scope; did you mean ‘loja::x’? | faltou o sobrenome (loja:: ou std::) |
reference to ‘count’ is ambiguous | um nome seu igual a um do std, com using namespace std; |
call of overloaded ‘f(int)’ is ambiguous | dois using namespace trazendo funções iguais |
redefinition of ‘double preco(int)’ | duas definições iguais no mesmo namespace |
Exercícios
1. Chamar pelo nome completo
A função desconto está no namespace loja. Complete a chamada:
2. Trazer um nome só
Complete para poder escrever cout sem std::, sem trazer o resto da biblioteca padrão:
3. Com using namespace std;, a Ana criou int count = 0; fora do main e recebeu reference to ‘count’ is ambiguous. Por quê?
4. Para que serve um namespace anônimo (namespace { … })?
5. Qual é o nome completo de minimo em namespace loja { namespace estoque { int minimo = 5; } }?
Missão final: a padaria ganha o seu namespace
Missão: a lojinha já tem o namespace loja, declarado no precos.h e definido no precos.cpp. Acrescente um namespace padaria com uma função double preco(int codigo) que devolve codigo * 2.0: declare no precos.h, defina no precos.cpp e mostre padaria::preco(10) no main.cpp. A saída deve ser:
Loja: 15 Padaria: 20
#include <iostream>
#include "precos.h"
int main() {
std::cout << "Loja: " << loja::preco(10) << '\n';
// mostre aqui o preço da padaria
}
#pragma once
namespace loja {
double preco(int codigo);
}
#include "precos.h"
namespace loja {
double preco(int codigo) {
return codigo * 1.5;
}
}
#include <iostream>
#include "precos.h"
int main() {
std::cout << "Loja: " << loja::preco(10) << '\n';
std::cout << "Padaria: " << padaria::preco(10) << '\n';
}
#pragma once
namespace loja {
double preco(int codigo);
}
namespace padaria {
double preco(int codigo);
}
#include "precos.h"
namespace loja {
double preco(int codigo) {
return codigo * 1.5;
}
}
namespace padaria {
double preco(int codigo) {
return codigo * 2.0;
}
}
Resumo
- Um namespace é um sobrenome para nomes:
loja::precoepadaria::precoconvivem sem conflito. ::separa o namespace do nome.stdé o namespace da biblioteca padrão;::xsozinho é o global.using loja::preco;traz um nome;using namespace loja;traz todos, e pode causarambiguous.using namespace std;mistura os seus nomes com centenas da biblioteca:countedistancedãoreference to … is ambiguous. Prefirastd::por extenso ouusing std::cout;, e nunca ponhausing namespacenum.h.- Namespaces podem ser aninhados (
loja::caixa, ounamespace loja::caixano C++17), reabertos em vários arquivos e ganhar apelidos (namespace lc = loja::caixa;). namespace { … }deixa as ajudantes de um.cppparticulares a ele, sem conflito na ligação.