Como você usa corretamente os espaços para nome em C ++?

231

Eu venho de um plano de fundo Java, onde pacotes são usados, não espaços para nome. Estou acostumado a reunir classes que trabalham juntas para formar um objeto completo em pacotes e, em seguida, reutilizá-las posteriormente a partir desse pacote. Mas agora estou trabalhando em C ++.

Como você usa namespaces em C ++? Você cria um único espaço para nome para o aplicativo inteiro ou para os principais componentes? Em caso afirmativo, como você cria objetos de classes em outros namespaces?

Marius
fonte

Respostas:

167

Namespaces são pacotes essencialmente. Eles podem ser usados ​​assim:

namespace MyNamespace
{
  class MyClass
  {
  };
}

Então no código:

MyNamespace::MyClass* pClass = new MyNamespace::MyClass();

Ou, se você quiser sempre usar um espaço para nome específico, faça o seguinte:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Edit: Seguindo o que bernhardrusch disse, eu tendem a não usar a sintaxe "using namespace x", geralmente especifico explicitamente o namespace ao instanciar meus objetos (ou seja, o primeiro exemplo que mostrei).

E, como você perguntou abaixo , você pode usar quantos espaços de nome desejar.

Mark Ingram
fonte
25
Na IMO, é melhor acostumar-se a prefixar o stdespaço de nomes para os símbolos do que usar using. Então, eu sempre escrevo std::coutou std::stringagora, porque é assim que eu os chamo agora. Eu nunca escreveria cout.
Tom Savage
5
Embora isso seja verdade std, eu pessoalmente achei isso muito menos importante quando você lida com bibliotecas menores. Geralmente, você pode usar using namespace FooBario;, principalmente se estiver usando um número considerável de tipos de uma biblioteca.
Jackian #
4
@kerker, entendo seu ponto de vista, mas discordo porque as colisões de nomes são (na minha cabeça) mais prováveis ​​de vir exatamente dessas bibliotecas pequenas. A maioria das pessoas toma cuidado para não nomear classes / funções iguais às do STL. Dito isto, concordo que using namespace X;deve ser evitado nos arquivos de cabeçalho, se possível.
Alan Turing
12
@ LexFridman "A maioria das pessoas toma cuidado para não nomear classes / funções iguais às do STL" - isso NÃO É VERDADEIRO. Por exemplo, se eu escrevesse algum código de E / S muito especializado para algum hardware estranho, nunca usaria nada além de mylibrary::endlrepresentar minha própria sequência de nova linha especial. Quero dizer, por que inventar nomes?
Meu compilador ainda não reconhecerá o espaço para nome, mesmo que eu queira especificá-lo explicitamente e incluir o arquivo onde ele é declarado.
bgenchel
116

Para evitar dizer tudo, Mark Ingram já disse uma pequena dica para usar namespaces:

Evite a diretiva "using namespace" nos arquivos de cabeçalho - isso abre o namespace para todas as partes do programa que importam esse arquivo de cabeçalho. Nos arquivos de implementação (* .cpp), isso normalmente não é um grande problema - embora eu prefira usar a diretiva "using namespace" no nível da função.

Acho que os namespaces são usados ​​principalmente para evitar conflitos de nomes - não necessariamente para organizar sua estrutura de código. Eu organizaria programas em C ++ principalmente com arquivos de cabeçalho / estrutura de arquivos.

Às vezes, os namespaces são usados ​​em projetos C ++ maiores para ocultar os detalhes da implementação.

Nota adicional à diretiva using: Algumas pessoas preferem usar "using" apenas para elementos únicos:

using std::cout;  
using std::endl;
Bernhardrusch
fonte
2
Uma vantagem de "usar espaço para nome" no nível da função, conforme sugerido, e não no nível do arquivo .cpp ou no nível do bloco do espaço para nome {} dentro do .cpp, é que ele ajuda bastante nas compilações de unidades de compilação única. "using namespace" é transitivo e aplica-se ao namespace A através dos blocos discretos de namespace A {} na mesma unidade; portanto, para compilações de unidades de compilação única, você acaba usando tudo rapidamente se for feito no nível de bloco de arquivo ou namespace.
Id
using std::cout; é uma declaração usando
Konstantin
3
É possível usar vários nomes de um único espaço para nome em uma única instrução? Algo parecido using std::cout, std::endl;ou mesmo using std::cout, endl;.
precisa saber é o seguinte
Pode ser bom usar a using namespace xem um cabeçalho se estiver em outro espaço para nome. Não é algo que eu recomendaria em geral, mas não polui o espaço para nome global.
Praxeolitic
79

Vincent Robert está certo em seu comentário Como você usa corretamente os namespaces em C ++? .

Usando espaço para nome

Os espaços para nome são usados ​​no mínimo para ajudar a evitar a colisão de nomes. Em Java, isso é imposto pelo idioma "org.domain" (porque supõe-se que não se use nada além de seu próprio nome de domínio).

No C ++, você pode atribuir um espaço para nome a todo o código do seu módulo. Por exemplo, para um módulo MyModule.dll, você pode atribuir ao código o espaço para nome MyModule. Eu já vi alguém usando MyCompany :: MyProject :: MyModule. Eu acho que isso é um exagero, mas apesar de tudo, parece correto para mim.

Usando "usando"

O uso deve ser usado com muito cuidado, pois importa efetivamente um (ou todos) símbolos de um espaço para nome no seu espaço para nome atual.

É ruim fazer isso em um arquivo de cabeçalho, porque seu cabeçalho poluirá todas as fontes, inclusive (ele me lembra macros ...) e, mesmo em um arquivo de origem, um estilo ruim fora do escopo de uma função, porque importará no escopo global os símbolos do espaço para nome.

A maneira mais segura de usar "using" é importar símbolos selecionados:

void doSomething()
{
   using std::string ; // string is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   std::cout << a << std::endl ;
}

void doSomethingElse()
{
   using namespace std ; // everything from std is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   cout << a << endl ;
}

Você verá muitos "using namespace std;" no tutorial ou códigos de exemplo. O motivo é reduzir o número de símbolos para facilitar a leitura, não porque é uma boa ideia.

"using namespace std;" é desencorajado por Scott Meyers (não me lembro exatamente de qual livro, mas posso encontrá-lo, se necessário).

Composição do namespace

Namespaces são mais que pacotes. Outro exemplo pode ser encontrado na "The C ++ Programming Language" de Bjarne Stroustrup.

Na "Edição Especial", na 8.2.8 Composição do Espaço para Nome , ele descreve como você pode mesclar dois espaços para nome AAA e BBB em outro chamado CCC. Assim, o CCC se torna um alias para AAA e BBB:

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

namespace CCC
{
   using namespace AAA ;
   using namespace BBB ;
}

void doSomethingAgain()
{
   CCC::doSomething() ;
   CCC::doSomethingElse() ;
}

Você pode até importar símbolos selecionados de diferentes espaços para nome, para criar sua própria interface de espaço para nome personalizado. Ainda não encontrei um uso prático disso, mas, em teoria, é legal.

paercebal
fonte
Você poderia esclarecer, por favor "forneça um espaço para nome para todo o código do seu módulo"? O que é uma boa prática encapsular no módulo. Por exemplo, tenho classe de números complexos e funções externas relacionadas a números complexos. Essa classe e essas duas funções devem estar em um espaço para nome?
yanpas
74

Não vi nenhuma menção nas outras respostas, então aqui estão meus 2 centavos canadenses:

No tópico "usando espaço para nome", uma declaração útil é o alias do espaço para nome, permitindo que você "renomeie" um espaço para nome, normalmente para dar um nome mais curto. Por exemplo, em vez de:

Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::TheClassName foo;
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::AnotherClassName bar;

você pode escrever:

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;
Éric Malenfant
fonte
55

Não dê ouvidos a todas as pessoas dizendo que espaços para nome são apenas espaços para nome.

Eles são importantes porque são considerados pelo compilador para aplicar o princípio da interface. Basicamente, isso pode ser explicado por um exemplo:

namespace ns {

class A
{
};

void print(A a)
{
}

}

Se você quisesse imprimir um objeto A, o código seria este:

ns::A a;
print(a);

Observe que não mencionamos explicitamente o espaço para nome ao chamar a função. Este é o princípio da interface: o C ++ considera uma função que aceita um tipo como argumento como parte da interface desse tipo; portanto, não é necessário especificar o espaço para nome porque o parâmetro já implicava no espaço para nome.

Agora, por que esse princípio é importante? Imagine que o autor da classe A não forneceu uma função print () para esta classe. Você terá que fornecer um você mesmo. Como você é um bom programador, você definirá essa função em seu próprio espaço para nome, ou talvez no espaço para nome global.

namespace ns {

class A
{
};

}

void print(A a)
{
}

E seu código pode começar a chamar a função de impressão (a) onde quiser. Agora imagine que anos depois, o autor decida fornecer uma função print (), melhor que a sua, porque conhece os aspectos internos da classe e pode criar uma versão melhor que a sua.

Os autores do C ++ decidiram que sua versão da função print () deveria ser usada em vez da fornecida em outro espaço de nome, para respeitar o princípio da interface. E que esse "upgrade" da função print () deve ser o mais fácil possível, o que significa que você não precisará alterar todas as chamadas para a função print (). É por isso que "funções de interface" (função no mesmo espaço para nome de uma classe) podem ser chamadas sem especificar o espaço para nome no C ++.

E é por isso que você deve considerar um namespace C ++ como uma "interface" ao usar um e ter em mente o princípio da interface.

Se você deseja uma melhor explicação desse comportamento, consulte o livro Excepcional C ++ de Herb Sutter

Vincent Robert
fonte
23
Você realmente precisa alterar todas as chamadas para print () se ns :: Print for adicionado, mas o compilador sinalizará cada chamada como ambígua. Mudar silenciosamente para a nova função seria uma péssima idéia.
Eclipse
Estou pensando agora, tendo o que @Vincent disse que você terá que alterar todas as chamadas para impressão, se o autor forneceria a função ns :: Print (), o que você estava tentando dizer? Que quando o autor adicionou uma função ns :: Print (), você pode simplesmente remover sua própria implementação? Ou que você simplesmente adiciona usando ns :: print () using-statement? Ou algo mais? Obrigado
Vaska el gato
36

Projetos maiores em C ++ que eu já vi dificilmente usavam mais de um namespace (por exemplo, biblioteca de impulso).

Na verdade, o boost usa toneladas de namespaces, normalmente cada parte do boost possui seu próprio espaço para o funcionamento interno e, em seguida, pode colocar apenas a interface pública no aumento do namespace de nível superior.

Pessoalmente, acho que quanto maior a base de código se tornar, mais os espaços de nome se tornarão, mesmo em um único aplicativo (ou biblioteca). No trabalho, colocamos cada módulo de nosso aplicativo em seu próprio espaço para nome.

Outro uso (sem trocadilhos) de namespaces que eu uso muito é o namespace anônimo:

namespace {
  const int CONSTANT = 42;
}

É basicamente o mesmo que:

static const int CONSTANT = 42;

Usar um espaço para nome anônimo (em vez de estático) é, no entanto, a maneira recomendada de código e dados ficarem visíveis apenas na unidade de compilação atual em C ++.


fonte
13
Ambos os seus exemplos são equivalentes const int CONSTANT = 42;porque a const de nível superior em um escopo de espaço para nome já implica em ligação interna. Portanto, você não precisa do espaço para nome anônimo neste caso.
sellibitze
19

Além disso, observe que você pode adicionar a um espaço para nome. Isso é mais claro com um exemplo, o que quero dizer é que você pode ter:

namespace MyNamespace
{
    double square(double x) { return x * x; }
}

em um arquivo square.he

namespace MyNamespace
{
    double cube(double x) { return x * x * x; }
}

em um arquivo cube.h. Isso define um único espaço para nome MyNamespace(ou seja, você pode definir um único espaço para nome em vários arquivos).

OysterD
fonte
11

Em Java:

package somepackage;
class SomeClass {}

Em C ++:

namespace somenamespace {
    class SomeClass {}
}

E usando-os, Java:

import somepackage;

E C ++:

using namespace somenamespace;

Além disso, os nomes completos são "somepackge.SomeClass" para Java e "somenamespace :: SomeClass" para C ++. Usando essas convenções, você pode organizar como está acostumado em Java, incluindo a criação de nomes de pastas correspondentes para namespaces. Os requisitos de pasta-> pacote e arquivo-> classe ainda não existem, portanto, você pode nomear suas pastas e classes independentemente dos pacotes e espaços para nome.

Staale
fonte
6

@ marius

Sim, você pode usar vários espaços para nome por vez, por exemplo:

using namespace boost;   
using namespace std;  

shared_ptr<int> p(new int(1));   // shared_ptr belongs to boost   
cout << "cout belongs to std::" << endl;   // cout and endl are in std

[Fevereiro 2014 - (Realmente faz tanto tempo?): Este exemplo em particular agora é ambíguo, como Joey aponta abaixo. Boost e std :: agora cada um tem um shared_ptr.]

Adam Hollidge
fonte
2
Observe que stdtambém já existe shared_ptr, portanto, o uso de namespaces booste de ambos stdentrará em conflito ao tentar usar a shared_ptr.
Joey
2
Este é um bom exemplo de por que muitas casas de software desencorajam a importação de namespaces inteiros dessa maneira. Não custa sempre especificar o espaço para nome e, se eles forem muito longos, crie um alias ou apenas classes específicas importantes a partir do espaço para nome.
arroz
5

Você também pode conter "using namespace ..." dentro de uma função, por exemplo:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}
Shadow2531
fonte
3

De um modo geral, crio um espaço para nome de um corpo de código, se acredito que possa haver conflitos de nome de função ou tipo com outras bibliotecas. Também ajuda a codificar a marca, ala boost :: .

Adam Hollidge
fonte
3

Prefiro usar um namespace de nível superior para o aplicativo e sub namespaces para os componentes.

A maneira como você pode usar classes de outros namespaces é surpreendentemente muito semelhante à maneira em java. Você pode usar "use NAMESPACE", que é semelhante a uma instrução "import PACKAGE", por exemplo, use std. Ou você especifica o pacote como prefixo da classe separado por "::", por exemplo, std :: string. Isso é semelhante ao "java.lang.String" em Java.

dmeister
fonte
3

Observe que um espaço para nome em C ++ realmente é apenas um espaço para nome. Eles não fornecem o encapsulamento que os pacotes fornecem em Java, portanto você provavelmente não os utilizará tanto.

Kristopher Johnson
fonte
2

Eu usei namespaces C ++ da mesma maneira que em C #, Perl, etc. É apenas uma separação semântica de símbolos entre itens de biblioteca padrão, itens de terceiros e meu próprio código. Eu colocaria meu próprio aplicativo em um espaço para nome e, em seguida, um componente de biblioteca reutilizável em outro espaço para nome para separação.

Spoulson
fonte
2

Outra diferença entre java e C ++, é que, em C ++, a hierarquia de namespaces não precisa maquinar o layout do sistema de arquivos. Portanto, costumo colocar uma biblioteca reutilizável inteira em um único espaço para nome e subsistemas dentro da biblioteca em subdiretórios:

#include "lib/module1.h"
#include "lib/module2.h"

lib::class1 *v = new lib::class1();

Eu só colocaria os subsistemas em namespaces aninhados se houvesse a possibilidade de um conflito de nomes.

KeithB
fonte