Existe uma maneira melhor de expressar namespaces aninhados em C ++ dentro do cabeçalho


97

Troquei de C ++ para Java e C # e acho que o uso de namespaces / pacotes é muito melhor lá (bem estruturado). Então voltei para o C ++ e tentei usar namespaces da mesma maneira, mas a sintaxe necessária é horrível no arquivo de cabeçalho.

namespace MyCompany
{
    namespace MyModule
    {
        namespace MyModulePart //e.g. Input
        {
            namespace MySubModulePart
            {
                namespace ...
                {
                    public class MyClass    

O seguinte parece estranho para mim também (para evitar o recuo profundo):

namespace MyCompany
{
namespace MyModule
{
namespace MyModulePart //e.g. Input
{
namespace MySubModulePart
{
namespace ...
{
     public class MyClass
     {

Existe uma maneira mais curta de expressar a coisa acima? Estou perdendo algo como

namespace MyCompany::MyModule::MyModulePart::...
{
   public class MyClass

Atualizar

Ok, alguns dizem que o conceito de uso em Java / C # e C ++ é diferente. Realmente? Eu acho que o carregamento de classe (dinâmico) não é o único propósito para namespaces (esta é uma perspectiva muito técnica). Por que não devo usá-lo para uma legibilidade e estruturação, por exemplo, pense em "IntelliSense".

Atualmente, não há lógica / ligação entre um namespace e o que você pode encontrar nele. Java e C # fazem isso muito melhor ... Por que incluir <iostream>e ter namespace std? Ok, se você diz que a lógica deve depender do cabeçalho a ser incluído, por que o #include não usa uma sintaxe amigável "IntelliSense" como #include <std::io::stream>ou <std/io/stream>? Acho que a estruturação ausente nas bibliotecas padrão é um ponto fraco do C ++ em comparação com Java / C #.

Se a exclusividade para conflitos ávidos é um ponto (que é um ponto de C # e Java também), uma boa ideia é usar o nome do projeto ou o nome da empresa como namespace, você não acha?

Por um lado, diz-se que C ++ é o mais flexível ... mas todos disseram "não faça isso"? Parece-me que C ++ pode fazer muitas coisas, mas tem uma sintaxe horrível até mesmo para as coisas mais fáceis em muitos casos, em comparação com C #.

Atualização 2

A maioria dos usuários diz que é um absurdo criar um aninhamento mais profundo do que dois níveis. Ok, então o que dizer dos namespaces Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives no desenvolvimento do Win8? Acho que o uso de namespaces pela Microsoft faz sentido e é mais profundo do que apenas 2 níveis. Acho que bibliotecas / projetos maiores precisam de um aninhamento mais profundo (odeio nomes de classes como ExtraLongClassNameBecauseEveryThingIsInTheSameNameSpace ... então você poderia colocar tudo no namespace global também.)

Atualização 3 - Conclusão

A maioria diz "não faça isso", mas ... mesmo o boost tem um aninhamento mais profundo do que um ou dois níveis. Sim, é uma biblioteca, mas: Se você deseja código reutilizável - trate seu próprio código como uma biblioteca que você daria a outra pessoa. Eu também uso um aninhamento mais profundo para fins de descoberta usando namespaces.


3
É um abuso de namespacepalavra-chave?
Nawaz

4
namespaces e sistemas de módulo c # / java não têm o mesmo propósito, portanto, você não deve tentar usá-los da mesma maneira. e não, não existe uma sintaxe mais simples, simplesmente porque não faz sentido fornecer uma sintaxe para tornar as coisas mais fáceis de fazer, que não devem ser feitas.
PlasmaHH

@PlasmaHH ... então o ponto fraco é a falta de estruturação da biblioteca padrão do C ++? (veja meu exemplo simples na atualização)
Beachwalker

@Stegi: Se você puder dar argumentos sólidos por que está faltando e quais benefícios sólidos obteríamos com essa estruturação, poderíamos falar sobre as fraquezas em potencial. Até então, eu diria que javas interminável aninhamento de pacotes confuso, na melhor das hipóteses.
PlasmaHH

3
@PlasmaHH Intellisense e outros auxiliares para / após a inclusão do cabeçalho (pacote). Grandes projetos dentro de uma empresa podem precisar de mais de um aninhamento (por exemplo, vw :: golflib :: io) para uma declaração clara do que um namespace contém em qual "escopo". Bem, você poderia apenas usar vw ::, mas se o namespace deve ser usado para evitar conflitos, por que são tão horríveis de declarar? Isso acaba a um ponto em que ninguém o usa ou apenas usa o namespace com a profundidade de um (como geralmente o sugere).
Beachwalker

Respostas:


130

C ++ 17 pode simplificar a definição de namespace aninhado:

namespace A::B::C {
}

é equivalente a

namespace A { namespace B { namespace C {
} } }

Consulte (8) na página do namespace em cppreference:
http://en.cppreference.com/w/cpp/language/namespace


5
... habilitado pelo switch do compilador/std:c++latest
lua ensolarada de

3
Observe que se você usar /std:c++latestno Visual Studio 2015 e também usar Boost, poderá encontrar erros de compilador muito místicos ao incluir alguns cabeçalhos Boost. Enfrentei esse problema conforme descrito nesta pergunta StackOverflow
Vivit

1
Funciona como está, namespace A :: B :: C. Eu testei com g ++ - 6.0
ervinbosenbacher


17

Apoio totalmente a resposta de Peterchen, mas gostaria de acrescentar algo que responda a outra parte de sua pergunta.

Declarar namespaces é um dos casos muito raros em C ++ em que realmente gosto do uso de #defines.

#define MY_COMPANY_BEGIN  namespace MyCompany { // begin of the MyCompany namespace
#define MY_COMPANY_END    }                     // end of the MyCompany namespace
#define MY_LIBRARY_BEGIN  namespace MyLibrary { // begin of the MyLibrary namespace
#define MY_LIBRARY_END    }                     // end of the MyLibrary namespace

Isso também elimina a necessidade de comentários próximos à chave de fechamento do namespace (você já rolou para baixo até o final de um arquivo de origem grande e tentou adicionar / remover / balancear chaves que estavam faltando comentários sobre qual chave fecha qual escopo? Não é divertido .).

MY_COMPANY_BEGIN
MY_LIBRARY_BEGIN

class X { };

class Y { };

MY_LIBRARY_END
MY_COMPANY_END

Se você quiser colocar todas as declarações de namespace em uma única linha, também pode fazer isso com um pouco da mágica do pré-processador (muito feia):

// helper macros for variadic macro overloading
#define VA_HELPER_EXPAND(_X)                    _X  // workaround for Visual Studio
#define VA_COUNT_HELPER(_1, _2, _3, _4, _5, _6, _Count, ...) _Count
#define VA_COUNT(...)                           VA_HELPER_EXPAND(VA_COUNT_HELPER(__VA_ARGS__, 6, 5, 4, 3, 2, 1))
#define VA_SELECT_CAT(_Name, _Count, ...)       VA_HELPER_EXPAND(_Name##_Count(__VA_ARGS__))
#define VA_SELECT_HELPER(_Name, _Count, ...)    VA_SELECT_CAT(_Name, _Count, __VA_ARGS__)
#define VA_SELECT(_Name, ...)                   VA_SELECT_HELPER(_Name, VA_COUNT(__VA_ARGS__), __VA_ARGS__)

// overloads for NAMESPACE_BEGIN
#define NAMESPACE_BEGIN_HELPER1(_Ns1)             namespace _Ns1 {
#define NAMESPACE_BEGIN_HELPER2(_Ns1, _Ns2)       namespace _Ns1 { NAMESPACE_BEGIN_HELPER1(_Ns2)
#define NAMESPACE_BEGIN_HELPER3(_Ns1, _Ns2, _Ns3) namespace _Ns1 { NAMESPACE_BEGIN_HELPER2(_Ns2, _Ns3)

// overloads for NAMESPACE_END
#define NAMESPACE_END_HELPER1(_Ns1)               }
#define NAMESPACE_END_HELPER2(_Ns1, _Ns2)         } NAMESPACE_END_HELPER1(_Ns2)
#define NAMESPACE_END_HELPER3(_Ns1, _Ns2, _Ns3)   } NAMESPACE_END_HELPER2(_Ns2, _Ns3)

// final macros
#define NAMESPACE_BEGIN(_Namespace, ...)    VA_SELECT(NAMESPACE_BEGIN_HELPER, _Namespace, __VA_ARGS__)
#define NAMESPACE_END(_Namespace, ...)      VA_SELECT(NAMESPACE_END_HELPER,   _Namespace, __VA_ARGS__)

Agora você pode fazer isso:

NAMESPACE_BEGIN(Foo, Bar, Baz)

class X { };

NAMESPACE_END(Baz, Bar, Foo) // order doesn't matter, NAMESPACE_END(a, b, c) would work equally well

Foo::Bar::Baz::X x;

Para aninhar mais de três níveis, você teria que adicionar macros auxiliares até a contagem desejada.


Por mais que eu não #definegoste, estou bastante impressionado com a mágica do pré-processador ... apenas se eu não precisar adicionar macros auxiliares adicionais para aninhamento mais profundo ... bem, não vou usá-lo de qualquer maneira .. .
galdin

12

Os namespaces C ++ são usados ​​para agrupar interfaces, não para dividir componentes ou expressar divisão política.

O padrão se esforça para proibir o uso de namespaces como o Java. Por exemplo, apelidos de namespace fornecem uma maneira de usar nomes de namespace longos ou profundamente aninhados.

namespace a {
namespace b {
namespace c {}
}
}

namespace nsc = a::b::c;

Mas namespace nsc {}então seria um erro, porque um namespace só pode ser definido usando seu nome de namespace original . Essencialmente, o padrão facilita as coisas para o usuário de tal biblioteca, mas difícil para o implementador . Isso desencoraja as pessoas de escrever tais coisas, mas atenua os efeitos se o fizerem.

Você deve ter um namespace por interface definido por um conjunto de classes e funções relacionadas. Subinterfaces internas ou opcionais podem entrar em namespaces aninhados. Mas mais de dois níveis de profundidade deve ser uma bandeira vermelha muito séria.

Considere o uso de caracteres de sublinhado e prefixos de identificador onde o ::operador não é necessário.


17
Ok, então o que dizer dos namespaces Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives no desenvolvimento do Win8? Acho que o uso de namespaces pela Microsoft faz sentido e é mais profundo do que apenas 2 níveis.
Beachwalker

2
Usar menos de 2 níveis é uma bandeira vermelha e usar 3 ou 4 é perfeitamente adequado. Tentar obter uma hierarquia de namespaces simples quando isso não faz sentido anula o próprio propósito dos namespaces - evitar conflitos de nomes. Concordo que você deve ter um nível para uma interface e outro para subinterfaces e internos. Mas em torno disso você precisa de pelo menos mais um nível para o namespace da empresa (para pequenas e médias empresas) ou dois para empresa e divisão (para grandes empresas). Caso contrário, seus namespaces de interface entrarão em conflito com os de outras interfaces com o mesmo nome desenvolvidas em outro lugar
Kaiserludi

@Kaiserludi Qual é a vantagem técnica do company::divisionover company_division?
Potatoswatter de

@Potatoswatter Dentro da empresa :: anotherDivsion você pode usar a 'divisão' mais curta. para se referir a company :: division mesmo dentro dos cabeçalhos, onde você deve evitar poluir os namespaces de nível superior com o uso de 'using namespace'. Fora do namespace da empresa, você ainda pode fazer um 'using namespace company;' quando os nomes da divisão não colidem com nenhum outro namespace em seu escopo, mas quando os nomes de interface dentro de algum namespace da divisão colidem de forma que você não pode fazer 'using namespace company_division;'
Kaiserludi

3
@Potatoswatter O ponto é que você o obtém praticamente de graça (company :: division não é mais longo que company_division) e não precisa definir um apelido de namespace extra primeiro para usá-lo.
Kaiserludi de

6

Não, e por favor, não faça isso.

O objetivo dos namespaces é principalmente resolver conflitos no namespace global.

Um propósito secundário é a abreviação local de símbolos; por exemplo, um UpdateUImétodo complexo pode usar um using namespace WndUIpara usar símbolos mais curtos.

Estou em um projeto 1.3MLoc e os únicos namespaces que temos são:

  • bibliotecas COM externas importadas (principalmente para isolar conflitos de cabeçalho entre #importe #include windows.h)
  • Um nível de namespaces de "API pública" para certos aspectos (IU, acesso ao banco de dados etc.)
  • Namespaces "Implementation Detail" que não fazem parte da API pública (namespaces anônimos em .cpp's ou ModuleDetailHereBeTygersnamespaces em libs apenas de cabeçalho)
  • enums são o maior problema em minha experiência. Eles poluem como loucos.
  • Ainda sinto que são muitos namespaces

Neste projeto, nomes de classes etc. usam um código de "região" de duas ou três letras (por exemplo, em CDBNodevez de DB::CNode). Se você preferir o último, há espaço para um segundo nível de namespaces "públicos", mas não mais.

Enums específicos de classe etc. podem ser membros dessas classes (embora eu concorde que isso nem sempre é bom e às vezes é difícil dizer se você deve)

Raramente há necessidade de um namespace de "empresa", exceto se você estiver tendo grandes problemas com bibliotecas de terceiros que são distribuídas como binárias, não fornecem seu próprio namespace e não podem ser facilmente colocadas em um (por exemplo, em um binário distribuição). Ainda assim, na minha experiência, forçá- los a um namespace é muito mais fácil de fazer.


[editar] De acordo com a pergunta de acompanhamento de Stegi:

Ok, então o que dizer dos namespaces Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives no desenvolvimento do Win8? Acho que o uso de namespaces pela Microsoft faz sentido e é realmente mais profundo do que apenas 2 níveis

Desculpe se não fui claro o suficiente: dois níveis não é um limite rígido e mais não é intrinsecamente ruim. Eu só queria salientar que raramente você precisa de mais de dois, por experiência própria, mesmo em uma grande base de código. Aninhar mais fundo ou mais superficial é uma troca.

Agora, o caso da Microsoft é indiscutivelmente diferente. Presumivelmente, uma equipe muito maior e todo o código é uma biblioteca.

Eu presumo que a Microsoft está imitando aqui o sucesso da Biblioteca .NET, onde os namespaces contribuem para a descoberta da extensa biblioteca. (.NET tem cerca de 18.000 tipos.)

Além disso, eu assumiria que existe uma (ordem de magnitude) de símbolos ideais em um namespace. digamos, 1 não faz sentido, 100 parece certo, 10000 é claramente muito.


TL; DR: É uma troca e não temos números concretos. Jogue pelo seguro, não exagere em nenhuma direção. O "Não faça isso" vem meramente do "Você tem problemas com isso, eu teria problemas com isso e não vejo uma razão para que você precise".


8
Ok, então o que dizer dos namespaces Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives no desenvolvimento do Win8? Acho que o uso de namespaces pela Microsoft faz sentido e é mais profundo do que apenas 2 níveis.
Beachwalker

2
Se preciso de constantes de acesso global, gosto de colocá-las em um namespace com um nome como e Constants, a seguir, criar namespaces aninhados com nomes apropriados para categorizar as constantes; se necessário, uso mais namespaces para evitar colisões de nomes. Este Constantsnamespace está contido em um namespace pega-tudo para o código do sistema do programa, com um nome como SysData. Isto cria um nome completo contendo três ou quatro espaços de nome (tal como SysData::Constants::ErrorMessages, SysData::Constants::Ailments::Bitflags, ou SysData::Defaults::Engine::TextSystem).
Hora de Justin - Reintegração de Monica em

1
Quando as constantes são exigidas no código real, qualquer função que precisa delas usa uma usingdiretiva para trazer os nomes apropriados, minimizando a possibilidade de nomes conflitantes. Acho que melhora a legibilidade e ajuda a documentar as dependências de qualquer bloco de código. Além das constantes, tenho tendência a tentar mantê-lo em dois namespaces, se possível (como SysData::Exceptionse SysData::Classes).
Justin Time - Reintegrar Monica

2
Como um todo, eu diria que, em casos gerais, é melhor usar um número mínimo de namespaces aninhados, mas se você precisar de objetos globais por algum motivo (seja constante ou mutável, de preferência o primeiro), vários namespaces aninhados devem ser usados para separá-los em categorias apropriadas, tanto para documentar seu uso quanto para minimizar potenciais colisões de nomes.
Justin Time - Reintegrar Monica em

2
-1 para "por favor, não faça isso" sem razões objetivas (apesar dos esclarecimentos posteriores). A linguagem oferece suporte a namespaces aninhados e um projeto pode ter bons motivos para usá-los. Uma discussão das possíveis razões e quaisquer desvantagens objetivas e concretas de fazê-lo reverteria meu voto negativo.
TypeIA

4

Aqui está uma citação de documentos Lzz (Lazy C ++):

Lzz reconhece as seguintes construções C ++:

definição de namespace

Um namespace sem nome e todas as declarações incluídas são produzidos no arquivo de origem. Esta regra substitui todas as outras.

O nome de um namespace nomeado pode ser qualificado.

   namespace A::B { typedef int I; }

é equivalente a:

   namespace A { namespace B { typedef int I; } }

Claro que a qualidade das fontes que dependem de tais ferramentas é discutível ... Eu diria que é mais uma curiosidade, mostrando que a doença de sintaxe induzida por C ++ pode assumir várias formas (eu também tenho a minha ...)


2

Ambos os padrões (C ++ 2003 e C ++ 11) são muito explícitos de que o nome do namespace é um identificador. Isso significa que cabeçalhos aninhados explícitos são necessários.

Minha impressão é que não é grande coisa permitir a colocação de um identificador qualificado ao lado de um simples nome do namespace, mas por algum motivo isso não é permitido.


1

Você pode usar esta sintaxe:

namespace MyCompany {
  namespace MyModule {
    namespace MyModulePart //e.g. Input {
      namespace MySubModulePart {
        namespace ... {
          class MyClass;
        }
      }
    }
  }
}

// Here is where the magic happens
class MyCompany::MyModule::MyModulePart::MySubModulePart::MyYouGetTheIdeaModule::MyClass {
    ...
};

Observe que essa sintaxe é válida mesmo em C ++ 98 e é quase semelhante ao que está agora disponível em C ++ 17 com definições de namespace aninhadas .

Feliz desaninhamento!

Fontes:


Esse é o sistema mencionado na pergunta, em que se procura uma solução melhor. Agora, com C ++ 17 é uma alternativa válida disponível conforme declarado pela resposta aceita. Desculpe, voto negativo por não ler a pergunta e a resposta.
Beachwalker

@Beachwalker não vamos nos prender à sintaxe. A declaração de namespace acima também pode ser a mesma da resposta aceita. O ponto principal que gostaria de destacar com esta resposta é o que o OP disse que não percebeu e o que eu fiz abaixo dessa bagunça de namespace. Pelo que posso ver, todos parecem estar focados em declarar tudo dentro do namespace, enquanto minha resposta tira você da confusão aninhada e tenho certeza de que o OP teria apreciado essa sintaxe se alguém a tivesse mencionado há 4 anos quando esta pergunta foi perguntado primeiro.
smac89

1

Este artigo cobre o assunto muito bem: Documento de namespace

O que basicamente se resume a isso. Quanto mais longos forem os seus namespaces, maior será a probabilidade de as pessoas usarem ousing namespace diretiva.

Então, olhando para o código a seguir, você pode ver um exemplo em que isso irá prejudicá-lo:

namespace abc { namespace testing {
    class myClass {};
}}

namespace def { namespace testing {
    class defClass { };
}}

using namespace abc;
//using namespace def;

int main(int, char**) {
    testing::myClass classInit{};
}

Este código irá compilar bem, no entanto, se você descomentar a linha //using namespace def;, o namespace de "teste" se tornará ambíguo e você terá conflitos de nomenclatura. Isso significa que sua base de código pode ir de estável para instável incluindo uma biblioteca de terceiros.

Em C #, mesmo que você use using abc;e using def;o compilador seja capaz de reconhecer isso testing::myClassou mesmo apenas myClassesteja no abc::testingnamespace, mas C ++ não reconhecerá isso e será detectado como uma colisão.


0

Sim, você terá que fazer como

namespace A{ 
namespace B{
namespace C{} 
} 
}

No entanto, você está tentando usar os namespaces de uma maneira que eles não deveriam ser usados. Verifique esta questão, talvez você a considere útil.


-1

[EDIT:]
Desde c ++ 17 namespaces aninhados são suportados como um recurso de linguagem padrão ( https://en.wikipedia.org/wiki/C%2B%2B17 ). A partir de agora, esse recurso não é compatível com g ++ 8, mas pode ser encontrado no compilador clang ++ 6.0.


[CONCLUSÃO:]
Use clang++6.0 -std=c++17como seu comando de compilação padrão. Então, tudo deve funcionar bem - e você poderá compilar com namespace OuterNS::InnerNS1::InnerNS2 { ... }seus arquivos.


[RESPOSTA ORIGINAL:]
Como esta pergunta é um pouco antiga, presumo que você mudou. Mas para outros, que ainda estão procurando uma resposta, tive a seguinte ideia:

Buffers do Emacs mostrando o arquivo principal, arquivos de namespace, comando / resultado de compilação e execução de linha de comando.

(Posso fazer um anúncio do Emacs aqui :)?) Postar uma imagem é muito mais fácil e mais legível do que simplesmente postar o código. Não pretendo fornecer uma resposta completa de todos os casos esquivos, simplesmente pretendia dar alguma inspiração. (Eu apoio totalmente C # e sinto que em muitos casos C ++ deve adotar alguns recursos OOP, já que C # é popular principalmente devido à sua facilidade de uso comparada).


Atualização: como o C ++ 17 aninhava namespacing ( nuonsoft.com/blog/2017/08/01/c17-nested-namespaces ), parece que minha resposta não é mais relevante, a menos que você esteja usando versões anteriores de C ++ .
ワ イ き ん ぐ

1
Por mais que eu ame o Emacs, postar uma imagem não é o ideal. Ele evita a pesquisa / indexação de texto e também torna sua resposta difícil de acessar para visitantes com deficiência visual.
Heinrich apoia Monica de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.