isolamento del nome dello spazio dei nomi C ++

-3

È stato preso in considerazione per un motivo, che using per namespace / nomi sia incline all'effetto collaterale e, in generale, i nomi completi devono essere preferiti.

Ho trovato un approccio su questo, che non ho ancora trovato implementato, quindi ho creato il mio progetto .

In breve, l'idea è di definire un insieme di macro che si spiegherebbe nella seguente struttura:

namespace your_namespace {
    namespace __local__ {
            //includes and other non-exported names go here
        namespace __exported__ {
            //actual code goes here
        }
    }
    using namespace __local__::__exported__;
}

Isola il contenuto di __local__ da your_namespace , mantenendoli visibili alla sua parte logicamente-effettiva.

L'utilizzo generale è simile a questo:

#include <namespace_util.hpp>

NAMESPACE(foo)
//here go using declarations, and the names you don't want to be visible from outside
NAMESPACE_EXPORTS
//here go your namespace members
NAMESPACE_END

L'implementazione:

#define NAMESPACE(name)\
    namespace name {\
        namespace __local__ {\

#define NAMESPACE_EXPORTS\
            namespace __exported__ {

#define NAMESPACE_END\
            }\
        }\
        using namespace __local__::__exported__;\
    }

Dato che sono relativamente nuovo al C ++, non posso essere completamente sicuro della soluzione, quindi chiedo alla gente: vedi il (molto più dettagliato) README , codice e fornisci il tuo feedback.

Grazie.

    
posta oleg.lukyrych 19.04.2017 - 18:11
fonte

6 risposte

10

Il tuo approccio non è tecnicamente sbagliato (tranne un dettaglio minore, vedi nota), ma va contro i modelli di utilizzo stabiliti e offusca il codice. Cioè riduce la leggibilità e aumenta la probabilità di errori. Che potrebbe essere giustificato se è superato da un vantaggio chiaro e significativo da qualche altra parte. E qui sta il problema. Sjoerd lo ha già indicato nella sua risposta, ma vorrei ribadire: in particolare qual è il problema che stai cercando di risolvere? Perché la tua soluzione è significativamente migliore rispetto a quella consolidata? Un vantaggio non è del tutto evidente, quindi considerando gli inconvenienti questo approccio non è utile.

Il problema più grave che vedo sono le macro di NAMESPACE. Sostituiscono elementi strutturali C ++ di base, essenzialmente creando una lingua all'interno della lingua. La struttura degli spazi dei nomi e dei nomi di simboli all'interno è una parte centrale dell'API che un'intestazione espone. Nascondere questa struttura rende molto più difficile capire cosa sta succedendo in quell'intestazione.

Guardando il modello stesso, non sono ancora convinto. Ora hai reso più chiaro in alcuni commenti e nella tua domanda aggiornata che desideri poter utilizzare i nomi dallo spazio dei nomi __local__ senza qualificare i loro nomi e senza gli effetti transitivi di using namespace . Da un lato questo può davvero essere un vantaggio per la leggibilità, specialmente se evita interruzioni di riga scomode a causa di nomi lunghi e qualificati. D'altra parte uno specifico using o using namespace all'interno di una funzione ha lo stesso effetto dove è veramente necessario. Anche dichiarare i propri simboli esterni in uno spazio dei nomi figlio di uno spazio dei nomi interno e poi estrarli di nuovo con un using non è molto elegante. Tutto sommato, trovo il modello non convincente, ma soprattutto innocuo (non riguardo ai macro!).

Per confronto: questo è il modello idiomatico per nascondere i simboli privati, ad esempio uno sviluppatore che ha familiarità con l'ecosistema C ++ si aspetta di vederlo e intuitivamente sa cosa significa:

// some_file.cpp
namespace my_ns {
    namespace {
        // Most private symbols go here.
        const int foo = 1;
    }

    int double_foo() {
        return foo * 2;
    }
}


// some_file.hpp
namespace my_ns {
    namespace detail {
        // Private names that cannot be hidden in the .cpp
        // go here. Other common names for this namespace are:
        // "impl", "implementation", sth with "private"
        const int bar = 2;
    }

    int double_foo();

    // function that cannot be implemented in the .cpp
    template <typename T>
    void add_bar(const T& to) {
        using namespace detail;
        // or use the qualified name instead
        to += bar;
    }
}

Nota a piè di pagina Dettagli tecnici minori: molti nomi sottolineati sono riservati per l'implementazione (dello standard). Vedi lo standard , sezione [reserved.names] per i dettagli completi. Nel tuo codice i nomi con doppia sottolineatura sono problematici. La regola "da non perdere" è la seguente: non creare i propri nomi iniziando con un carattere di sottolineatura ed evitare del tutto il doppio di underscore.

    
risposta data 20.04.2017 - 11:30
fonte
8

Sembra un approccio sbagliato, per diversi motivi:

  1. Le cose che non vuoi essere "visibili" all'esterno, non dovrebbero entrare nelle intestazioni. Invece entrano nello spazio dei nomi anonimo nel file sorgente.
  2. Le tue __local__ cose sono ancora visibili e chiunque può accedervi
risposta data 19.04.2017 - 18:41
fonte
6

Che problema vuoi risolvere? Questo non è completamente chiaro.

Per quanto riguarda il tuo codice, se tu fossi un junior assegnato a me, direi:

  • Il tuo approccio non nasconde affatto i dettagli di implementazione - sono ancora visibili nell'intestazione.

  • Il tuo approccio nasconde le cose dietro le macro in stile C, che sono considerate "evitate quando possibile" dalla comunità C ++. Quindi la sostituzione di una soluzione che non utilizza macro con una soluzione che utilizza macro, sta andando nella direzione sbagliata.

  • dichiari

    using for namespaces/names is side-effect-prone

    ma scrivi using namespace te stesso. Quindi sei incoerente.

Dopo aver indicato quegli errori di base, non avrei dedicato più tempo al tuo codice. Invece, tornerei alla domanda principale: quale problema vuoi risolvere?

    
risposta data 19.04.2017 - 23:18
fonte
4

Se vuoi davvero un feedback costruttivo su questo approccio, sarebbe stato meglio mostrare del codice reale che pensi possa trarne beneficio. Quindi avremmo qualcosa di concreto da discutere piuttosto che ipotetici.

Poiché nel tuo README collegato c'è qualcosa di simile a un esempio di utilizzo, questo dovrebbe essere stato nella domanda: è utile vedere e inserire le parti essenziali della domanda nei link esterni funziona male. Non ho nemmeno notato che il link era lì fino a dopo aver letto sia la domanda che tutte le risposte esistenti, e anche il link rot è una preoccupazione.

Ora, osservando l'utilizzo del campione:

#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP

#include <namespace_util.hpp>

INLINE_NAMESPACE(myproject, MYPROJECT_MYCLASS_HPP)

using namespace std;

NAMESPACE_EXPORTS

class MyClass {

};

NAMESPACE_END

#endif

Le mie prime riflessioni, che si avvicinano a questo senza preavviso, sono:

  1. Che diavolo è questo?
  2. Oh bene, qualcun altro ha scritto una nuova sotto-lingua nel preprocessore. Almeno questa volta non è una macro FOREACH rotta in modo orribile.
  3. Scommetto che anche loro hanno scritto un APPUNTAMENTO da qualche parte, dovrò tenerlo d'occhio per questo.
  4. grep -r '#define private public' ... nessuna corrispondenza, che sollievo
  5. È impossibile dire a prima vista come usare la classe - a meno che non abbia già familiarità con il tuo linguaggio macro, mi fa lavorare di più per capire che dovrebbe essere chiamato myproject::MyClass
  6. questo non sembra risparmiare molto tempo o fatica semplicemente scrivendolo direttamente, con commenti esplicativi.

    Cioè, la versione macro-free qui sotto non è molto prolissa, non è meno chiara (entrambi confondono la prima volta che li vedi, e una volta che sei abituato all'idioma comunque, i macro sono non più chiaro)

Versione senza macro:

#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPP

namespace myproject {
namespace myclass_detail { // external dependencies go here
  using namespace std;     // scope limited to myclass_extern
  namespace publ {         // public declarations in here
    class MyClass {};
  }
}
using namespace myclass_detail::publ;
}

#endif
    
risposta data 20.04.2017 - 14:43
fonte
1

Lo scopo degli spazi dei nomi in C ++ è di evitare conflitti di nomi tra codice da librerie diverse (non correlate). Per esempio. libA può definire una classe o una funzione chiamata libA::foo senza scontrarsi con libB::foo . Inoltre, la versione 2 di libA può introdurre un nome che è già utilizzato in libB. Il codice in libB, così come gli altri codici che usano sia libA che libB, continueranno a compilare e comportarsi allo stesso modo quando aggiorniamo la libA dalla versione 1 alla versione 2.

Quando introduci using namespace ... nel tuo codice, perdi la protezione offerta da uno spazio dei nomi (appropriato). Quando si aggiorna una libreria il cui spazio dei nomi è stato importato, il codice potrebbe non riuscire a compilare, a causa di conflitti di nomi. Oppure (peggio), il tuo codice potrebbe ancora compilare, ma comportarsi in modo diverso, ad es. perché durante il sovraccarico viene selezionata una diversa funzione di sovraccarico.

Questo problema non è risolto dallo schema di namespace proposto. Quando libB dipende da libA e importa il suo spazio dei nomi, l'aggiornamento di libA potrebbe causare il fallimento della compilazione (o il comportamento scorretto) della libb.

Quindi, lo schema proposto per l'uso degli spazi dei nomi ha uno scopo solo quando il rilascio delle librerie dipendenti è gestito collettivamente. Cioè libB viene controllato per compilare e comportarsi allo stesso ogni volta che libA viene modificata e / o libB viene aggiornato per evitare errori ogni volta che libA viene modificata. Tuttavia, in questo caso è discutibile il motivo per cui queste librerie utilizzano spazi dei nomi diversi in primo luogo.

    
risposta data 21.04.2017 - 12:09
fonte
-1

Quando ricevo un certo grado di feedback, ritengo che non sia molto più prevedibile, quindi mi piacerebbe riassumere tutto quanto detto.

Una parte della critica ha indicato che il problema che sto cercando di risolvere non è chiaro.

Esiste l'effetto transitivo di using namespace/name . Secondo me, può essere indesiderato in alcune situazioni, ma non esiste un modo standard per farlo scomparire. Questo è il problema che sto cercando di risolvere. Quindi, ho proposto un modello per questo scopo:

namespace your_namespace {
    namespace __local__ {
        //names not intended to be in your_namespace, including those introduced by "using" directive should be here
        //they will be visible to the de facto body of your_namespace, while not being exposed
        namespace __exported__ { //will be inlined into your_namespace
            //everything you'd like to have in your_namespace
        }
    }
    using namespace __local__::__exported__; //the inlining
}

L'attualità è discutibile.

Sebbene le pratiche classiche di gestione dei nomi funzionino, potrebbero implicare verbosità e forzare un certo design. Secondo me, il modello è ragionevolmente semplice, innocuo e garantisce la libertà nel senso che rende alcuni stili di codifica non errori. È stato abbastanza notato che le applicazioni non sono chiare. Li interromperò nelle seguenti categorie:

  1. Riduzione della piastra di calpestio. La fonte principale di verbosità sono i nomi complessi - nodi dell'albero del nome con profondità > 1, dove 0 è il globale stesso. Possono venire dal tuo codice e vengono spesso da loro codice esterno che si desidera riutilizzare. Nel primo caso, non è un problema finché non hai un albero dei nomi molto complicato. In quest'ultimo, non dipende da te e potrebbe potenzialmente essere abbastanza significativo. Immagina una libreria che devi usare, che usa pesantemente namespace (giustificato dalla complessità), come Boost. Potresti finire con codice come:

    //library.hpp
    namespace library {
        namespace foo {
            struct A;
            namespace bar {
                struct B;
                namespace baz {
                    struct C;
                    struct D;
                    //...
                }
            }
        }
        //... Hypothetically endlessly complicated name tree
    }
    //module1.hpp
    //includes library.hpp
    namespace myproject {
        // can't use using directive, because it may cause name conflicts in other modules,
        // and in the namespaces using "myproject" namespace
        library::foo::A someFn(library::foo::bar::B, library::foo::bar::baz::C, library::foo::bar::baz::D);
        library::foo::bar::baz::C someOtherFn(library::foo::bar::baz::C, library::foo::bar::baz::C, library::foo::bar::baz::D);
        struct E {
            library::foo::A a;
            library::foo::bar::B b;
            library::foo::bar::baz::C c;
            library::foo::bar::baz::D d;
            //...
        };
        //... Hypothetically endless number of inevitable endlessly complex and varying names usages
        //... Hypothetically endlessly complicated name tree
    }
    //module2.hpp
    //includes some other name-complex libraries
    namespace myproject {
        //... Hypothetically endless number of differently named members (not conflicting with existing actual members)
        //... Hypothetically endless number of inevitable endlessly complex and varying names usages
    }
    

    È notevolmente semplificato con il modello:

    //module1.hpp
    //includes library.hpp
    namespace myproject {
        namespace module_1 {
            using namespace library::foo; //these directives won't impact "myproject"
            using namespace bar;
            using namespace baz;
            namespace __exported__ {
                A someFn(B, C, D); //users of the function would have to explicitly deal with external names, which is right in my vision
                C someOtherFn(C, C, D);
                struct E {
                    A a;
                    B b;
                    C c;
                    D d;
                };
            }
        }
        using namespace module_1::__exported__;
    } 
    

    Questo era un esempio di progetto one-namespace. È ancora più reale quando c'è un namespace per modulo e dipendono i moduli l'uno sull'altro. Anche questo è stato un esempio di classico header-con-dichiarazioni-sources-con-definizioni. Quando progetti una libreria di sola intestazione, quindi c'è un ulteriore carico di gestione nomi lunghi ad es. nei corpi delle funzioni.
    Nonostante esistano soluzioni alternative, hanno dei difetti:

    • I typedef non sono applicabili a tutti i tipi di nomi e introducono un nome nello spazio dei nomi

    • L'alias dello spazio dei nomi non è ancora un nome breve ed è esso stesso un nome nel namespace.

    • L'utilizzo di direttive nell'ambito della funzione probabilmente si duplicherà, poiché diverse funzioni spesso funzionano con gli stessi nomi.

  2. incapsulamento. Questo approccio consente non solo di isolare le importazioni, ma anche di avere membri privati dello spazio dei nomi.

  3. Razionalizzazione del design. Nessuno metterà in discussione la virtù della modularità. Mentre ci sono meccanismi per implementare la modularità del codice, vale a dire, diversi file e inclusione, non penso sia sufficiente. I nomi sono nell'essenza di un modulo: la sua struttura logica. C ++ consente il nome fisico di un modulo (nome file) e il nome logico divergente, ma penso che dovrebbero essere coerenti. E i nomi nel modulo non dovrebbero essere mescolati con i nomi da cui dipende. Mi pentirò di dirlo, ma in altri sistemi di moduli, ad es. ES6 o Java, i moduli hanno nomi complessi per una migliore identificazione, ad es. packageName.shortName, che sono coerenti con i loro nomi fisici; hanno nomi interiori, che esportano, e i nomi che consumano (importa), e questi ultimi non sono mai in conflitto con il primo; Personalmente, adotto queste viste e non vedo come questo concetto sia in conflitto con C ++.

L'utilizzo di macro è sconsigliato.

Ho creato macro come supplemento all'idea. Il modello può essere utilizzato senza di loro. Creandoli ero distorto alla percezione individuale, dove era familiare, e considerato più dichiarativo e amp; codice pulito. Finché questa cosa non è standardizzata all'interno di un gruppo, dovrebbe essere, ovviamente, evitata dai membri del gruppo.

Incapsulamento insufficiente.

L'utilizzo diretto di your_namespace::__local__ avrebbe rovinato tutto. Ecco perché l'ho oscurato tramite il nome sottolineato. Questa tecnica di incapsulamento è puramente semantica, quindi non completamente affidabile: chiunque può ancora fare riferimento a quel nome. Sfortunatamente, è il massimo che ottieni qui. Ad ogni modo, penso che il buon senso suggerisca di non usare qualcosa chiamato come __do_not_touch_me__ .

L'utilizzo di nomi underscored è sconsigliato.

Sì, viola le raccomandazioni dello standard e dovrebbe essere cambiato.

Ringrazio tutti per aver partecipato!

    
risposta data 21.04.2017 - 18:18
fonte

Leggi altre domande sui tag