2.8 – nomear colisões e uma introdução aos espaços de nomes

digamos que você está dirigindo para a casa de um amigo pela primeira vez, e o endereço dado a você é 245 Front Street em Mill City. Ao chegar a Mill City, você puxa seu mapa, só para descobrir que Mill City realmente tem duas ruas Frontais diferentes em frente da cidade um do outro! A qual é que vais? A menos que houvesse alguma pista adicional para ajudá-lo a decidir (e.g. você se lembra que sua casa é perto do rio) você teria que ligar para seu amigo e pedir mais informações. Porque isso seria confuso e ineficiente (especialmente para seu carteiro), na maioria dos países, todos os nomes de rua e endereços de casa dentro de uma cidade são necessários para ser único.

similarmente, c++ requer que todos os identificadores não sejam ambíguos. Se dois identificadores idênticos são introduzidos no mesmo programa de uma forma que o compilador ou linker não pode distingui-los, o compilador ou linker irá produzir um erro. Este erro é geralmente referido como uma colisão de nome (ou conflito de nome).

um exemplo de uma nomenclatura colisão

a.cpp:

1
2
3
4
5
6

#include <iostream>
void myFcn(int x)
{
std::cout << x;
}

principal.cpp:

1
2
3
4
5
6
7
8
9
10
11

#include <iostream>
void myFcn(int x)
{
std::cout << 2 * x;
}
int main()
{
retorno 0;
}

Quando o compilador compila este programa, ele irá compilar a.cpp e o principal.cpp independentemente, e cada arquivo irá compilar sem problemas.

no entanto, quando o linker executa, irá ligar todas as definições em a.cpp e main.cpp juntos, e descobrir definições conflitantes para a função myFcn. O linker irá então abortar com um erro. Note que este erro ocorre mesmo que myFcn nunca é chamado!

a maioria das colisões de nomenclatura ocorrem em dois casos:
1) duas (ou mais) definições para uma função (ou variável global) são introduzidas em arquivos separados que são compilados no mesmo programa. Isto resultará em um erro de linker, como mostrado acima.
2) duas (ou mais) definições para uma função (ou variável global) são introduzidas no mesmo arquivo (muitas vezes através de uma #include). Isto resultará em um erro de compilador.

à medida que os programas ficam maiores e usam mais identificadores, as chances de uma colisão de nome sendo introduzida aumenta significativamente. A boa notícia é que o C++ fornece muitos mecanismos para evitar colisões de nomes. O escopo Local, que mantém as variáveis locais definidas dentro das funções de conflito entre si, é um desses mecanismos. Mas o âmbito local não funciona para nomes de funções. Então, como mantemos os nomes das funções de conflito entre si?

o que é um espaço de nomes?De volta à nossa analogia de endereço por um momento, ter duas ruas da frente só foi problemático porque essas ruas existiam dentro da mesma cidade. Por outro lado, se você tivesse que entregar o correio a dois endereços, um na rua front 209 em Mill City, e outro endereço na rua Front 417 em Jonesville, não haveria confusão sobre para onde ir. Dito de outra forma, as cidades fornecem agrupamentos que nos permitem desambiguar endereços que de outra forma poderiam entrar em conflito uns com os outros. Os espaços de nomes agem como as cidades fazem nesta analogia.

um espaço de nomes é uma região que permite que você declare nomes dentro dele para o propósito de desambiguação. O espaço de nomes fornece um escopo (chamado espaço de nomes) para os nomes declarados dentro dele — o que significa simplesmente que qualquer nome declarado dentro do espaço de nomes não será confundido com nomes idênticos em outros âmbitos.

key insight

um nome declarado num espaço de nomes não será confundido com um nome idêntico declarado noutro âmbito.

dentro de um espaço de nomes, Todos os nomes devem ser únicos, caso contrário uma colisão de nome resultará.

Namespaces são frequentemente usados para agrupar identificadores relacionados em um grande projeto para ajudar a garantir que eles não colidem inadvertidamente com outros identificadores. Por exemplo, se você colocar todas as suas funções matemáticas em um espaço de nomes chamado matemática, então suas funções matemáticas não vão colidir com funções identicamente nomeadas fora do espaço de nomes de matemática.

falaremos sobre como criar seus próprios espaços de nomes em uma lição futura.

O espaço de nomes global

Em C++, qualquer nome que não é definido dentro de uma classe, função, ou um espaço de nomes é considerado parte de uma definidas implicitamente namespace chamado o espaço de nomes global (às vezes também chamado de escopo global).

no exemplo no topo da lição, funções principais() e ambas as versões do myFcn() são definidas dentro do espaço de nomes global. A colisão de nome encontrada no exemplo acontece porque ambas as versões do myFcn () acabam dentro do espaço de nomes global, o que viola a regra de que todos os nomes no espaço de nomes devem ser únicos.

o espaço de nomes std

quando c++ foi originalmente desenhado, todos os identificadores na Biblioteca Padrão C++ (incluindo std:: cin e std:: cout) estavam disponíveis para serem usados sem o prefixo std::: (eles faziam parte do espaço de nomes global). No entanto, isso significava que qualquer identificador na biblioteca padrão poderia potencialmente entrar em conflito com qualquer nome que você escolheu para seus próprios identificadores (também definido no espaço de nomes global). O código que estava funcionando pode de repente ter um conflito de nome quando você # incluiu um novo arquivo da biblioteca padrão. Ou pior, programas que compilariam sob uma versão de C++ podem não compilar sob uma versão futura de C++, como novos identificadores introduzidos na biblioteca padrão poderiam ter um conflito de nome com código já escrito. Então o c++ moveu toda a funcionalidade da biblioteca padrão para um espaço de nomes chamado “std” (abreviação para standard).

acontece que std:: o nome de cout não é realmente std:: cout. Na verdade, é apenas cout, e std é o nome do espaço de nomes do qual o identificador cout faz parte. Como o cout é definido no espaço de nomes do std, o nome cout não entra em conflito com nenhum objeto ou função chamado cout que criamos no espaço de nomes global.

Similarly, when accessing an identifier that is defined in a namespace (e.g. std::cout) , you need to tell the compiler that we’re looking for an identifier defined inside the namespace (std).

key insight

quando você usa um identificador que é definido dentro de um espaço de nomes (como o espaço de nomes std), você tem que dizer ao compilador que o identificador vive dentro do espaço de nomes.

há algumas maneiras diferentes de fazer isso.

qualificador de espaço de nomes explícito std::

a maneira mais simples de dizer ao compilador que queremos usar o cout do espaço de nomes std é usando explicitamente o prefixo std::. Por exemplo:

1
2
3
4
5
6
7

#include <iostream>
int main()
{
std::cout << “Olá, mundo!”; // when we say cout, we mean the cout defined in the std namespace
return 0;
}

o:: símbolo é um operador chamado operador de resolução de escopo. O identificador à esquerda do símbolo :: identifica o espaço de nomes que o nome à direita do símbolo :: está contido dentro. Se não for fornecido nenhum identificador à esquerda do símbolo::, assume-se o espaço de nomes global.

so when we say std:: cout, we’re saying “the cout that lives in namespace std”.

esta é a maneira mais segura de usar cuut, porque não há ambiguidade sobre qual cout estamos Referenciando (a que está no espaço de nomes std).

Best practice

Use prefixos explícitos de espaços de nomes para aceder aos identificadores definidos num espaço de nomes.

usando std de espaço de nomes (e por que evitá-lo)

outra maneira de acessar identificadores dentro de um espaço de nomes é usar uma declaração de uso da Diretiva. Aqui está o nosso programa original “Hello world” com uma directiva de Utilização:

1
2
3
4
5
6
7
8
9

#include <iostream>
usando o namespace std; // esta é uma diretiva usando dizendo ao compilador para verificar o espaço de nomes std quando a resolução de identificadores com nenhum prefixo
int main()
{
cout << “Olá, mundo!”; // cout não tem prefixo, assim, o compilador irá verificar se o cout é definida localmente ou no espaço de nomes std
voltar 0;
}

usando Uma diretiva diz ao compilador para verificar se um determinado espaço de nomes quando se tenta resolver um identificador que não tem prefixo de espaço de nomes. Assim, no exemplo acima, quando o compilador vai determinar qual é o identificador cout, ele irá verificar tanto localmente (onde está indefinido) quanto no espaço de nomes std (onde irá corresponder ao std::cout).

muitos textos, tutoriais e até mesmo alguns compiladores recomendam ou usam uma diretiva de uso no topo do programa. No entanto, usado desta forma, esta é uma má prática, e altamente desencorajado.

considere o seguinte programa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include <iostream> // importa a declaração de std::cout
usando o namespace std; // faz std::cout acessível como “cout”
int cout() // declara a nossa própria “cout” função
{
voltar 5;
}
int main()
{
cout << “Olá, mundo!”; / / Compile error! Qual é a gota que queremos aqui? O do espaço de nomes std ou o que definimos acima?
voltar 0;
}

O programa acima não compila, pois o compilador agora não pode dizer se queremos cout função que definimos, ou o cout é definida dentro do espaço de nomes std.

ao usar uma diretiva usando desta forma, qualquer identificador que definamos pode entrar em conflito com qualquer identificador identicamente nomeado no espaço de nomes std. Ainda pior, enquanto um nome identificador não pode entrar em conflito hoje, ele pode entrar em conflito com novos identificadores adicionados ao espaço de nomes std em futuras revisões de linguagem. Este era o ponto inteiro de mover todos os identificadores na biblioteca padrão para o espaço de nomes std em primeiro lugar!

Warning

evite o uso de diretivas (como o uso de std de espaço de nomes;) no topo do seu programa. Eles violam a razão pela qual os espaços de nomes foram adicionados em primeiro lugar.

vamos falar mais sobre o uso de declarações (e como usá-las responsavelmente) na lição 6.12 — usando declarações.

2.9 — Introdução para o pré-processador

Índice de

2.7 — Programas com vários arquivos de código

Deixe uma resposta

O seu endereço de email não será publicado.