Esiste un modo migliore per esprimere gli spazi dei nomi nidificati in C ++ all'interno dell'intestazione


97

Sono passato da C ++ a Java e C # e penso che l'uso di spazi dei nomi / pacchetti sia molto migliore lì (ben strutturato). Poi sono tornato a C ++ e ho provato a usare gli spazi dei nomi allo stesso modo, ma la sintassi richiesta è orribile all'interno del file di intestazione.

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

Anche quanto segue mi sembra strano (per evitare il rientro profondo):

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

C'è un modo più breve per esprimere quanto sopra? Mi manca qualcosa di simile

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

Aggiornare

Ok, alcuni dicono che il concetto di utilizzo in Java / C # e C ++ è diverso. Veramente? Penso che il caricamento delle classi (dinamico) non sia l'unico scopo per i namespace (questa è una prospettiva ragionata molto tecnica). Perché non dovrei usarlo per una leggibilità e strutturazione, ad esempio pensare a "IntelliSense".

Attualmente, non esiste alcuna logica / colla tra uno spazio dei nomi e ciò che puoi trovare lì. Java e C # lo fanno molto meglio ... Perché includere <iostream>e avere lo spazio dei nomi std? Ok, se dici che la logica dovrebbe fare affidamento sull'intestazione da includere, perché #include non utilizza una sintassi amichevole "IntelliSense" come #include <std::io::stream>o <std/io/stream>? Penso che la strutturazione mancante nelle librerie predefinite sia una debolezza di C ++ rispetto a Java / C #.

Se l'unicità di conflitti avidi è un punto (che è anche un punto di C # e Java) una buona idea è usare il nome del progetto o il nome dell'azienda come spazio dei nomi, non credi?

Da un lato si dice che il C ++ è il più flessibile ... ma tutti hanno detto "non farlo"? Mi sembra che il C ++ possa fare molte cose ma ha una sintassi orribile anche per le cose più semplici in molti casi rispetto a C #.

Aggiorna 2

La maggior parte degli utenti afferma che non ha senso creare un annidamento più profondo di due livelli. Ok, allora che dire degli spazi dei nomi Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives nello sviluppo di Win8? Penso che l'utilizzo degli spazi dei nomi da parte di Microsoft abbia senso ed è effettivamente più profondo di soli 2 livelli. Penso che librerie / progetti più grandi necessitino di un annidamento più profondo (odio i nomi di classi come ExtraLongClassNameBecauseEveryThingIsInTheSameNameSpace ... quindi potresti mettere tutto anche nello spazio dei nomi globale.)

Aggiornamento 3 - Conclusione

La maggior parte dice "non farlo", ma ... anche il boost ha una nidificazione più profonda di uno o due livelli. Sì, è una libreria ma: se vuoi codice riutilizzabile, tratta il tuo codice come una libreria che daresti a qualcun altro. Uso anche una nidificazione più profonda per scopi di scoperta utilizzando spazi dei nomi.


3
È un abuso di namespaceparole chiave?
Nawaz

4
spazi dei nomi e sistemi di moduli c # / java non hanno lo stesso scopo, quindi non dovresti provare a usarli allo stesso modo. e no, non esiste una sintassi più semplice, semplicemente perché non ha senso fornire una sintassi per rendere le cose più facili da fare, che non sono destinate a fare.
PlasmaHH

@PlasmaHH ... quindi il punto debole è la mancata strutturazione della libreria std di C ++? (vedi il mio semplice esempio all'interno dell'aggiornamento)
Beachwalker

@Stegi: Se puoi fornire valide argomentazioni sul perché manca e quali vantaggi concreti otterremmo da una tale strutturazione, potremmo parlare di potenziali punti deboli. Fino ad allora chiamerei javas l'annidamento infinito di pacchetti che confondono al massimo.
PlasmaHH

3
@PlasmaHH Intellisense e altri helper per / dopo l'inclusione dell'intestazione (pacchetto). Progetti di grandi dimensioni all'interno di una società potrebbero richiedere più di un annidamento (ad esempio vw :: golflib :: io) per una dichiarazione chiara di cosa contiene uno spazio dei nomi e in quale "ambito". Bene, potresti semplicemente usare vw :: ma se lo spazio dei nomi deve essere usato per evitare scontri, perché sono così orribili da dichiarare? Questo finisce al punto che nessuno lo usa o usa semplicemente lo spazio dei nomi con una profondità di uno (come spesso suggerisce).
Beachwalker

Risposte:


130

C ++ 17 potrebbe semplificare la definizione dello spazio dei nomi annidato:

namespace A::B::C {
}

è equivalente a

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

Vedere (8) nella pagina dello spazio dei nomi su cppreference:
http://en.cppreference.com/w/cpp/language/namespace


5
... abilitato dall'interruttore del compilatore/std:c++latest
luna piena di sole

3
Tieni presente che se utilizzerai /std:c++latestVisual Studio 2015 e utilizzerai anche Boost, potresti riscontrare errori del compilatore molto mistici quando includi alcune intestazioni Boost. Ho affrontato questo problema come descritto in questa domanda StackOverflow
Vivit

1
Funziona così com'è, spazio dei nomi A :: B :: C. Ho provato con g ++ - 6.0
ervinbosenbacher


17

Sostengo pienamente la risposta di Peterchen, ma voglio aggiungere qualcosa che affronti un'altra parte della tua domanda.

La dichiarazione degli spazi dei nomi è uno dei casi molto rari in C ++ in cui effettivamente mi piace l'uso di #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

Ciò elimina anche la necessità di commenti vicino alla parentesi graffa di chiusura dello spazio dei nomi (Hai mai fatto scorrere verso il basso fino alla fine di un file sorgente di grandi dimensioni e hai provato ad aggiungere / rimuovere / bilanciare le parentesi graffe che mancavano di commenti su quale parentesi graffa chiude quale ambito? .).

MY_COMPANY_BEGIN
MY_LIBRARY_BEGIN

class X { };

class Y { };

MY_LIBRARY_END
MY_COMPANY_END

Se vuoi mettere tutte le dichiarazioni dello spazio dei nomi su una singola riga, puoi farlo anche con un po 'di (piuttosto brutta) magia del preprocessore:

// 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__)

Ora puoi farlo:

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;

Per annidare più in profondità di tre livelli dovresti aggiungere macro di supporto fino al conteggio desiderato.


Per quanto non mi piaccia #define, sono abbastanza impressionato da quella magia del preprocessore ... solo se non dovessi aggiungere ulteriori macro di supporto per un annidamento più profondo ... beh, non lo userò comunque così .. .
galdin

12

Gli spazi dei nomi C ++ vengono utilizzati per raggruppare le interfacce, non per dividere componenti o esprimere divisione politica.

Lo standard fa di tutto per proibire l'uso di spazi dei nomi in stile Java. Ad esempio, gli alias dello spazio dei nomi forniscono un modo per utilizzare facilmente nomi di spazi dei nomi lunghi o profondamente nidificati.

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

namespace nsc = a::b::c;

Ma namespace nsc {}allora sarebbe un errore, perché uno spazio dei nomi può essere definito solo utilizzando il suo nome-spazio dei nomi originale . Essenzialmente lo standard rende le cose facili per l' utente di una simile libreria ma difficili per l' implementatore . Questo scoraggia le persone dallo scrivere cose del genere, ma ne mitiga gli effetti se lo fanno.

Dovresti avere uno spazio dei nomi per interfaccia definito da un insieme di classi e funzioni correlate. Le interfacce secondarie interne o facoltative potrebbero andare in spazi dei nomi annidati. Ma più di due livelli di profondità dovrebbero essere una bandiera rossa molto seria.

Prendi in considerazione l'utilizzo di caratteri di sottolineatura e prefissi identificativi in ​​cui l' ::operatore non è necessario.


17
Ok, allora che dire degli spazi dei nomi Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives nello sviluppo di Win8? Penso che l'utilizzo degli spazi dei nomi da parte di Microsoft abbia senso ed è effettivamente più profondo di soli 2 livelli.
Beachwalker

2
Usare meno di 2 livelli è una bandiera rossa e usare 3 o 4 va benissimo. Cercare di ottenere una gerarchia dello spazio dei nomi piatta quando non ha senso vanifica lo scopo stesso degli spazi dei nomi: evitare conflitti di nome. Sono d'accordo che dovresti avere un livello per un'interfaccia e un altro per le sottointerfacce e gli interni. Ma intorno a questo è necessario almeno un livello in più per lo spazio dei nomi dell'azienda (per le piccole e medie aziende) o due per l'azienda e la divisione (per le grandi aziende). Altrimenti gli spazi dei nomi dell'interfaccia si scontreranno con quelli di altre interfacce con lo stesso nome sviluppate altrove
Kaiserludi

@Kaiserludi Qual è il vantaggio tecnico company::divisionrispetto a company_division?
Potatoswatter

@Potatoswatter Inside company :: anotherDivsion puoi semplicemente usare la "divisione" più breve. per fare riferimento a company :: division anche all'interno di intestazioni dove dovresti evitare fortemente di inquinare i namespace di livello superiore utilizzando 'using namespace'. Al di fuori dello spazio dei nomi dell'azienda è ancora possibile eseguire un "utilizzo dello spazio dei nomi dell'azienda;" quando i nomi delle divisioni non entrano in collisione con altri spazi dei nomi nel tuo ambito, ma quando i nomi delle interfacce all'interno di alcuni spazi dei nomi delle divisioni entrano in collisione in modo che tu non possa fare "using namespace company_division;".
Kaiserludi

3
@Potatoswatter Il punto è che lo ottieni praticamente gratuitamente (company :: division non è più lungo di company_division) e non devi prima definire un alias spazio dei nomi aggiuntivo per usarlo.
Kaiserludi

6

No, e per favore non farlo.

Lo scopo degli spazi dei nomi è principalmente risolvere i conflitti nello spazio dei nomi globale.

Uno scopo secondario è l'abbreviazione locale dei simboli; ad esempio, un UpdateUImetodo complesso può utilizzare un using namespace WndUIper utilizzare simboli più brevi.

Sono su un progetto 1.3MLoc e gli unici spazi dei nomi che abbiamo sono:

  • librerie COM esterne importate (principalmente per isolare i conflitti di intestazione tra #importe #include windows.h)
  • Un livello di spazi dei nomi "API pubbliche" per determinati aspetti (interfaccia utente, accesso al database ecc.)
  • Spazi dei nomi "Dettagli implementazione" che non fanno parte dell'API pubblica (spazi dei nomi anonimi in .cpp o ModuleDetailHereBeTygersspazi dei nomi nelle librerie di sola intestazione)
  • le enumerazioni sono il problema più grande nella mia esperienza. Inquinano come un matto.
  • Sento ancora che sono troppi spazi dei nomi

In questo progetto, i nomi delle classi ecc. Usano un codice "regione" di due o tre lettere (ad es. CDBNodeInvece di DB::CNode). Se preferisci quest'ultimo, c'è spazio per un secondo livello di spazi dei nomi "pubblici", ma non di più.

Le enumerazioni specifiche della classe ecc. Possono essere membri di quelle classi (anche se sono d'accordo che non è sempre buono, ea volte è difficile dire se dovresti)

Raramente è necessario nemmeno uno spazio dei nomi "azienda", tranne se si hanno grossi problemi con le librerie di terze parti che sono distribuite come binarie, non forniscono il proprio spazio dei nomi e non possono essere facilmente inseriti in uno (ad esempio in un file binario distribuzione). Eppure, nella mia esperienza costringere loro in uno spazio dei nomi è molto più facile da fare.


[modifica] Secondo la domanda di follow-up di Stegi:

Ok, allora che dire degli spazi dei nomi Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives nello sviluppo di Win8? Penso che l'utilizzo degli spazi dei nomi da parte di Microsoft abbia senso ed è effettivamente più profondo di soli 2 livelli

Scusa se non sono stato abbastanza chiaro: due livelli non sono un limite rigido e altri non sono intrinsecamente negativi. Volevo solo sottolineare che raramente ne occorrono più di due, dalla mia esperienza, anche su una grande base di codice. La nidificazione più profonda o più superficiale è un compromesso.

Ora, il caso Microsoft è probabilmente diverso. Presumibilmente un team molto più grande e tutto il codice è libreria.

Presumo che Microsoft stia imitando qui il successo della libreria .NET, dove gli spazi dei nomi contribuiscono alla rilevabilità della vasta libreria. (.NET ha circa 18000 tipi.)

Suppongo inoltre che ci sia un ottimo (ordine di grandezza di) simboli in uno spazio dei nomi. diciamo, 1 non ha senso, 100 suona bene, 10000 è chiaramente troppo.


TL; DR: È un compromesso e non abbiamo numeri precisi. Gioca sul sicuro, non esagerare in nessuna direzione. Il "Non farlo" deriva semplicemente dal "Hai problemi con quello, io avrei problemi con quello, e non vedo un motivo per cui avresti bisogno.".


8
Ok, allora che dire degli spazi dei nomi Windows :: UI :: Xaml e Windows :: UI :: Xaml :: Controls :: Primitives nello sviluppo di Win8? Penso che l'utilizzo degli spazi dei nomi da parte di Microsoft abbia senso ed è effettivamente più profondo di soli 2 livelli.
Beachwalker

2
Se ho bisogno di costanti di accesso globale, mi piace metterle in uno spazio dei nomi con un nome simile Constants, quindi creare spazi dei nomi annidati con nomi appropriati per classificare le costanti; se necessario, utilizzo ulteriori spazi dei nomi per evitare conflitti di nomi. Questo Constantsspazio dei nomi è a sua volta contenuto in uno spazio dei nomi generico per il codice di sistema del programma, con un nome come SysData. Questo crea un nome completo contenente tre o quattro spazi dei nomi (come ad esempio SysData::Constants::ErrorMessages, SysData::Constants::Ailments::Bitflagso SysData::Defaults::Engine::TextSystem).
Justin Time - Ripristina Monica il

1
Quando le costanti sono richieste nel codice effettivo, qualsiasi funzione che ne abbia bisogno utilizza una usingdirettiva per inserire i nomi appropriati, riducendo al minimo la possibilità di nomi in conflitto. Trovo che migliori la leggibilità e aiuti a documentare le dipendenze di qualsiasi blocco di codice. A parte le costanti, tendo a mantenerlo su due spazi dei nomi, se possibile (come SysData::Exceptionse SysData::Classes).
Justin Time - Ripristina Monica il

2
Nel complesso, direi che in casi generali, è meglio utilizzare un numero minimo di spazi dei nomi nidificati, ma se per qualche motivo hai bisogno di oggetti globali (sia costanti che mutabili, preferibilmente il primo), dovrebbero essere usati più spazi dei nomi nidificati per separarli in categorie appropriate, sia per documentare il loro utilizzo che per ridurre al minimo le potenziali collisioni di nomi.
Justin Time - Ripristina Monica il

2
-1 per "per favore non farlo" senza ragioni oggettive (nonostante i chiarimenti in seguito). Il linguaggio supporta gli spazi dei nomi annidati e un progetto può avere buone ragioni per usarli. Una discussione su possibili ragioni di questo tipo e su qualsiasi svantaggio oggettivo e concreto di farlo annullerebbe il mio voto negativo.
TypeIA

4

Ecco una citazione dai documenti di Lzz (Lazy C ++):

Lzz riconosce i seguenti costrutti C ++:

definizione dello spazio dei nomi

Uno spazio dei nomi senza nome e tutte le dichiarazioni incluse vengono emesse nel file di origine. Questa regola prevale su tutte le altre.

Il nome di uno spazio dei nomi denominato può essere qualificato.

   namespace A::B { typedef int I; }

è equivalente a:

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

Ovviamente la qualità dei sorgenti che dipende da tali strumenti è discutibile ... direi che è più una curiosità, dimostrando che la malattia di sintassi indotta dal C ++ può assumere molte forme (anch'io ho la mia ...)


2

Entrambi gli standard (C ++ 2003 e C ++ 11) sono molto espliciti sul fatto che il nome dello spazio dei nomi è un identificatore. Ciò significa che sono necessarie intestazioni nidificate esplicite.

La mia impressione che questo non sia un grosso problema per consentire l'inserimento di identificatori qualificati oltre a un semplice nome dello spazio dei nomi, ma per qualche motivo ciò non è consentito.


1

Puoi usare questa sintassi:

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 {
    ...
};

Nota che questa sintassi è valida anche in C ++ 98 ed è quasi simile a ciò che è ora disponibile in C ++ 17 con definizioni di spazio dei nomi annidate .

Buon divertimento!

Fonti:


Questa è la sintassi menzionata nella domanda in cui si cerca invece una soluzione migliore. Ora, con C ++ 17 è disponibile una valida alternativa come dichiarato dalla risposta accettata. Scusa, voto negativo per non aver letto la domanda e la risposta.
Beachwalker

@Beachwalker cerchiamo di non farci prendere dalla sintassi. La dichiarazione dello spazio dei nomi sopra potrebbe anche essere la stessa della risposta accettata. Il punto principale che volevo sottolineare con questa risposta è ciò che l'OP aveva detto di essersi perso e ciò che ho fatto sotto quel pasticcio di spazio dei nomi. Per quanto posso vedere, sembra che tutti si siano concentrati sulla dichiarazione di tutto all'interno dello spazio dei nomi, mentre la mia risposta ti porta fuori dal caos annidato e sono sicuro che OP avrebbe apprezzato questa sintassi se qualcuno l'avesse menzionata 4 anni fa quando questa domanda è stato chiesto per la prima volta.
smac89

1

Questo documento copre l'argomento piuttosto bene: Namespace Paper

Il che fondamentalmente si riduce a questo. Più lunghi sono gli spazi dei nomi, maggiori sono le probabilità che le persone utilizzino la using namespacedirettiva.

Quindi guardando il seguente codice puoi vedere un esempio in cui questo ti farà male:

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{};
}

Questo codice verrà compilato correttamente, tuttavia, se si rimuove il commento dalla riga, //using namespace def;lo spazio dei nomi "testing" diventerà ambiguo e si verificheranno conflitti di denominazione. Ciò significa che la tua base di codice può passare da stabile a instabile includendo una libreria di terze parti.

In C #, anche se dovessi usare using abc;e using def;il compilatore è in grado di riconoscerlo testing::myClasso anche solo myClassè solo nello abc::testingspazio dei nomi, ma C ++ non lo riconoscerà e verrà rilevato come una collisione.


0

Sì, dovrai farlo così

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

Tuttavia, stai cercando di utilizzare gli spazi dei nomi in un modo in cui non dovrebbero essere utilizzati. Controlla questa domanda, forse la troverai utile.


-1

[EDIT:]
Poiché gli spazi dei nomi annidati c ++ 17 sono supportati come funzionalità del linguaggio standard ( https://en.wikipedia.org/wiki/C%2B%2B17 ). Al momento, questa funzione non è supportata in g ++ 8, ma può essere trovata nel compilatore in clang ++ 6.0.


[CONCLUSIONE:]
Usa clang++6.0 -std=c++17come comando di compilazione predefinito. Quindi tutto dovrebbe funzionare bene e sarai in grado di compilare connamespace OuterNS::InnerNS1::InnerNS2 { ... } tuoi file.


[RISPOSTA ORIGINALE:]
Poiché questa domanda è un po 'vecchia, presumo che tu sia andato avanti. Ma per altri, che stanno ancora cercando una risposta, ho avuto la seguente idea:

Buffer di Emacs che mostrano il file principale, i file dello spazio dei nomi, il comando / risultato della compilazione e l'esecuzione della riga di comando.

(Potrei fare una pubblicità per Emacs qui :)?) Pubblicare un'immagine è molto più facile e più leggibile che inserire semplicemente codice. Non intendo fornire una risposta completa a tutti i casi d'angolo, volevo semplicemente dare qualche ispirazione. (Sostengo totalmente C # e ritengo che in molti casi C ++ dovrebbe adottare alcune funzionalità OOP, dal momento che C # è popolare principalmente per la sua comparabile facilità d'uso).


Aggiornamento: poiché C ++ 17 ha spazi dei nomi annidati ( nuonsoft.com/blog/2017/08/01/c17-nested-namespaces ), sembrerebbe che la mia risposta non sia più pertinente, a meno che non si utilizzino versioni precedenti di C ++ .
ワ イ き ん ぐ

1
Per quanto io ami Emacs, pubblicare un'immagine non è l'ideale. Elude la ricerca / indicizzazione del testo e rende anche difficile l'accesso alla tua risposta per i visitatori ipovedenti.
Heinrich supporta Monica
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.