C ++ Spazi dei nomi estendibili: come forzare le dichiarazioni nello spazio dei nomi globale

6

È un buon stile di programmazione includere tutte le dipendenze necessarie in un'intestazione che le fa riferimento. Spesso questo include dichiarazioni che sono posizionate nel STD & namespace globali (come cstdio). Tuttavia, questo crea problemi quando un secondo programmatore vuole avvolgere un tale file di inclusione in un nuovo spazio dei nomi per incapsulare il primo.

Con questo scenario in mente, c'è un modo per forzare le dichiarazioni nello spazio dei nomi globale? Come esempio concettuale (questo in realtà non verrà compilato):

foo.h --> original definition
// force global namespace, this is redundant when foo is in the global space
//   but could be important if foo included into another namespace.
namespace :: { 
  #include <cstdio>
}
namespace foo {
  FILE *somefile;
}


bar.h --> New file, by a new programmer, encapsulating foo.
namespace bar {
  # include "foo.h"
  FILE *somefile;  // bar:somefile, as opposed to bar::foo::somefile
}

Senza il putativo namespace ::{} finiamo con dichiarazioni come foo::FILE e foo::printf() , quando vogliamo solo ::FILE e ::printf() .

Le librerie popolari come boost risolvono ciò definendo una gerarchia esplicita. Ciò richiederebbe che foo sappia che farà parte della barra e che lo spazio dei nomi globale includa al di fuori della definizione del namespace locale.

Tuttavia, una buona estensibilità richiede che siamo in grado di usare foo direttamente, OPPURE wrap foo in un nuovo spazio dei nomi, senza dover cambiare foo o include.

Qualcuno sa come farlo?

    
posta bgulko 29.09.2015 - 21:26
fonte

3 risposte

9

Includere un'intestazione in uno spazio dei nomi è incline a infrangere varie ipotesi e non dovrebbe essere fatto. I problemi che mi vengono in mente sono:

  • Si rompono le guardie. Se un'intestazione può essere inclusa solo una volta e la includi nello spazio dei nomi sbagliato, l'altro codice non può includerlo nello spazio dei nomi corretto all'interno della stessa unità di compilazione. O viceversa: se era già incluso in una libreria, non puoi ri-includerlo da qualche altra parte. Per esempio:.

    someLib / A.h

    #ifndef SOMELIB_A_H_
    #define SOMELIB_A_H_
    // assume this will be included into namespace someLib
    class A {};
    #endif
    

    someLib / B.h

    #ifndef SOMELIB_B_H_
    #define SOMELIB_B_H_
    namespace someLib
    {
        # include "A.h"
        int b(const A& a) { return 42; }
    }
    #endif
    

    usercode.cpp

    // defines someLib::b and somelib::A
    #include <somelib/B.h>
    // include A into global namespace
    // – won't work because someLib/A.h was already included
    #include <somelib/A.h>
    #include <iostream>
    int main()
    {
        std::cout << someLib::b(A()) << '\n';
        return 0;
    }
    

    Questo fallirà perché #include <someLib/A.h> è un no-op nel programma principale, e non include nulla. Pertanto, A non è definito nel programma principale. Se i file fossero inclusi al contrario, funzionerebbe poiché la dichiarazione someLib::b può anche vedere i simboli nell'ambito globale. Tuttavia, l'ordine degli include non dovrebbe avere importanza in una biblioteca ben progettata!

  • Non sarai in grado di collegarti. La maggior parte dei file di intestazione ha un file .cpp associato che include dettagli di implementazione privati. Se includi un'intestazione in uno spazio dei nomi diverso, non può collegarsi all'implementazione.

  • Spezza le macro. Se una macro fornita dalla libreria richiama in una libreria, dovrà sempre utilizzare il nome completo dello spazio dei nomi, poiché non sappiamo da quale spazio dei nomi verrà utilizzato. Se riesci a includere un simbolo foo::bar::BazException come qux::BazException , allora una macro perfettamente ragionevole come

    #define FOO_BAR_BAZEXCEPTION(expr) foo::bar::BazException(expr, __FILE__, __LINE__)
    

    smetterà di funzionare.

  • Interrompe la ricerca dipendente dall'argomento, che funziona solo se la funzione e i relativi argomenti condividono uno spazio dei nomi.

Se includere un file in uno spazio dei nomi è un'idea così orribile e romperà più cose di quante ne risolva, quali sono le alternative?

Prima di tutto, gli spazi dei nomi sono buoni. Ci aiutano a strutturare l'architettura generale, anche se sono molto aperti. Usiamo gli spazi dei nomi non solo perché impongono una comoda gerarchia che evita conflitti di nome, ma anche perché dipendono da alcune funzionalità come la ricerca dipendente da argomenti. L'unica ragione per cui la libreria standard inserisce alcuni simboli nello scope globale è la compatibilità con C. Anche in questo caso, puoi accedere ai simboli attraverso lo spazio dei nomi std:: , che dovrebbe essere preferito.

Alcuni ritengono che gli spazi dei nomi potrebbero diventare scomodi. Questo non è un problema perché possiamo definire alias più brevi nell'ambito in cui sono necessari con using foo::bar::Baz . Questa è anche la soluzione quando si mettono i simboli nello scope globale: un alias non nasconde il nome originale e completo, quindi tutti i problemi sopra menzionati non si applicano. La direttiva using ha molti usi e può avere alias namespace, nomi di tipo e valori come variabili o funzioni membro.

Quando includi un file, supponi sempre che sia correttamente assegnato ai nomi. Pertanto, includilo in ambito globale prima di aprire qualsiasi spazio dei nomi. Anche se le intestazioni provengono dal tuo progetto, assicurati che dichiarino i propri spazi dei nomi. Ciò significa anche che la struttura degli spazi dei nomi è abbastanza difficile da modificare una volta risolta. Ma ciò è positivo: qualsiasi modifica all'interfaccia pubblica (che include la struttura dello spazio dei nomi) richiederebbe anche la modifica di tutto il codice dipendente. Non vedo come "buona estensibilità" ci richiederebbe di rinunciare a questa struttura.

    
risposta data 30.09.2015 - 08:12
fonte
2

Semplice- non fare quella merda.

Includere un'intestazione in uno spazio dei nomi è una cosa stupida da fare e non dovrebbe essere fatto. Se non sei soddisfatto dello spazio dei nomi di un'intestazione, denunzia all'autore o forni l'intestazione. Includere l'intestazione in uno spazio dei nomi è completamente inefficace e inutile.

    
risposta data 29.09.2015 - 21:40
fonte
2

La risposta breve sembra essere che le gerarchie dello spazio dei nomi sono state progettate per essere definite staticamente in fase di progettazione e non utilizzate in una progettazione estesa. Cioè, non disponibile per il confezionamento in altri spazi dei nomi post-design. Quindi, sono più uno strumento per impedire al proprio codice di inquinare lo spazio globale, piuttosto che contenere l'inquinamento dello spazio dei nomi dal codice di altre persone. Credo che questa limitazione non sia necessaria, ma chiarisce la mia richiesta immediata.

Alcuni lavori aggiuntivi hanno fornito una soluzione parziale, ma la risposta breve è che gli spazi dei nomi non sono stati progettati per essere nidificati dopo design. Usarli in questo modo, anche se possibile, è contrario all'intento progettuale e quindi una cattiva idea. Detto questo, ecco un metodo per raggiungerlo.

Credo che la maggior parte dei problemi con il collegamento potrebbero essere risolti con due pratiche:

  1. Assicurarsi che gli accessi non in linea siano stati pubblicati nello spazio dei nomi globale.
  2. Estendendo solo spazi dei nomi in linea / intestazione

Il primo potrebbe essere eseguito ora creando un file name_ext.h per ogni spazio dei nomi e includendolo nell'ambito globale. Uno spazio dei nomi derivato avrebbe il proprio derivato_ext.h che include tutto il nome dipendente_ext.h, impiegando quindi più barriere di inclusione per assicurare che gli include globali fossero pubblicati nello spazio dei nomi globale. Ciò manterrebbe il collegamento della libreria binaria. Un'estensione namespace :: {} alle definizioni home nello spazio dei nomi globale all'interno di un blocco namespace esistente, risolverebbe in modo più elegante questo aspetto.

Il secondo punto riguarda il collegamento in fase di compilazione dei file .cpp. Ho provato questo e funziona.

L'unico problema rimasto è quello delle classi con classi multiple. Senza un sostanziale supporto del preprocessore per le barriere di inclusione dinamiche, renderebbe impossibile avere spazi dei nomi multi-homed, che è in definitiva un vero problema per l'ortogonalità del design.

Tuttavia, questo sarebbe comunque limitato agli spazi dei nomi completamente contenuti nei file include.

In ogni caso l'utilizzo di namespace in questo modo, anche se possibile, sembra antitetico alla filosofia attuale del design.

    
risposta data 01.10.2015 - 21:37
fonte

Leggi altre domande sui tag