2009-12-14 1 views
5

Va bene, questa domanda si è evoluto un po ', e voglio provare ad avviare (oltre), con gli obiettivi di base sto girando per:c Namespace ++ mal di testa

  • Creare codice della libreria che wrappers eredità linguaggio C le entità nell'acquisizione di risorse C++ sono inizializzate e forniscono anche una garanzia di eccezione di base o migliore.
  • Abilita i client di questo codice a utilizzarlo in modo C++ molto naturale senza creare un sovraccarico di codice esistente per convertirlo a utilizzare gli oggetti wrapper C++ (ovvero conversione automatica a tipi legacy appropriati, costruttori che ereditano legacy tipi, ecc.)
  • Limitare l'impatto sullo spazio dei nomi del codice della libreria. Idealmente, la libreria avrebbe diversi sottolivelli che forniscono funzionalità correlate che limitano il volume e l'impatto dell'uso delle dichiarazioni del tipo X dello spazio dei nomi - proprio come fanno le librerie di boost (cioè l'uso di spazi dei nomi dei dettagli per iniettare solo quei simboli che l'utente vorrebbe ragionevolmente per utilizzare e nascondere quelli che sono dettagli di implementazione, limitare anche la portata di possibili nuovi significati ai simboli esistenti, per evitare conversioni implicite sorprendenti all'interno del codice utente)
  • Richiedere che i client richiedano esplicitamente quelle parti della libreria che in realtà voglio iniettare nella loro base di codice. Questo va di pari passo con la limitazione dell'impatto dell'inclusione delle intestazioni della biblioteca. Il codice client dovrebbe avere un ragionevole livello di controllo su quali parti della libreria verranno automaticamente utilizzate per la risoluzione dei nomi quando compilano il loro codice.
  • Il mio codice libreria non dovrebbe essere pieno di costrutti di codice refactoring. Sarebbe ideale se le intestazioni della libreria non dovessero dichiarare costantemente typedef privati ​​per poter accedere al resto di quella sezione della libreria. O in altre parole: voglio che la mia libreria sia in grado di essere scritta in modo intuitivo come i miei clienti quando fanno uso di detta libreria. La risoluzione dei nomi dovrebbe includere lo spazio dei nomi in cui è definita la libreria, oltre a tutti gli altri che sono stati esplicitamente "using'd".

Ho incontrato questo scenario spesso, e sono alla ricerca di un modo migliore ...

Ho una classe, C nel namespace N. C ha un membro, libero. È gratis qualcosa che C gestisce e consente a C di gestire una cosa nuova.

Esistono diverse funzioni globali gratuite. Ci sono anche alcune funzioni di supporto nello stesso namespace N di C, una delle quali è un helper che libera la cosa gestita da C, chiamata free.

Così abbiamo qualcosa di simile:

namespace N { 

void free(THING * thing); 

class C 
{ 
public: 
    ... details omitted... 
    free() 
    { 
    free(m_thing); // <- how best to refer to N::free(THING&) 
    } 
} 

} // namespace N 

ho potuto utilizzare N :: gratuito (m_thing). Ma mi sembra sfortunato. Non c'è modo di riferirsi a ciò che è al di fuori dell'ambito della classe, ma senza risolvere lo spazio dei nomi assoluto (un relativo passo fuori dal campo di applicazione)?

Mi sembra che dover nominare N :: free sia odioso, poiché non sarebbe necessario se si trattasse di una funzione indipendente. Né sarebbe necessario se il nome del metodo della classe fosse diverso (ad es. Dispose). Ma poiché ho usato lo stesso nome, non posso accedervi senza dover specificare cosa rappresenta un percorso assoluto - piuttosto che un percorso relativo - se mi concederai l'analogia.

Odio i percorsi assoluti. Trasformano le cose in giro in spazi dei nomi molto fragili, quindi il refactoring del codice diventa molto più brutto. Inoltre, le regole su come denominare le cose nei corpi delle funzioni diventano più complesse con l'attuale serie di regole (come le comprendo) - meno regolari - inducendo uno scisma tra ciò che ci si aspetta e ciò che si ottiene come programmatore.

Esiste un modo migliore per accedere a funzioni indipendenti nello stesso spazio dei nomi di una classe senza dover assolutamente assegnare un nome assoluto alla funzione libera?

EDIT: Forse avrei dovuto andare con un esempio meno astratto:

namespace Toolbox { 
    namespace Windows { 

// deallocates the given PIDL 
void Free(ITEMIDLIST ** ppidl); 

class Pidl 
{ 
public: 
    // create empty 
    Pidl() : m_pidl(NULL) { } 

    // create a copy of a given PIDL 
    explicit Pidl(const ITEMIDLIST * pidl); 

    // create a PIDL from an IShellFolder 
    explicit Pidl(IShellFolder * folder); 

    ... 

    // dispose of the underlying ITEMIDLIST* so we can be free to manage another... 
    void Free(); 
}; 

Così ITEMIDLIST * provengono da una varietà di luoghi, e sono distrutti con CoTaskMemFree(). Potrei introdurre Pidl come nome globale - così come tutte le funzioni di aiuto nell'intestazione "Windows Shell.h" che fa parte della mia libreria di toolbox.

Idealmente, segmenterei alcuni degli strumenti nella mia libreria in base a ciò a cui si riferiscono - in questo caso, il tutto si riferisce alla programmazione COM in Windows. Ho scelto Toolbox come spazio dei nomi di base per le mie librerie e stavo pensando che avrei usato Toolbox :: Windows per funzioni, classi, ecc. Di windows-y

Ma lo spazio dei nomi C++ e le regole di risoluzione dei nomi sembrano per rendere questo molto difficile (quindi questa domanda). Rende molto innaturale creare tale segmentazione del mio codice - dato che la ricerca di koenig fallisce (dal momento che ITEMIDLIST non si trova nel mio spazio degli strumenti Toolbox :: Windows), e non ho la possibilità di spostarlo lì! Il linguaggio dovrebbe essere abbastanza flessibile, IMO, per consentire sia alle librerie di estensioni come la mia libreria Toolbox di estendere il codice di altre persone senza dover iniettare le mie estensioni nel loro spazio dei nomi (che, nel caso di Win32 e il vasto generale la maggior parte del codice che esiste oggi, è il GLOBAL NS - che è il punto principale della creazione degli spazi dei nomi in primo luogo: evitare l'affollamento globale di NS/l'inquinamento/l'ambiguità/le sorprese di programmazione).

Quindi, torno in giro a, c'è un modo migliore per fare questo: estendere librerie esistenti di codice, mentre non inquinante loro NS con le mie estensioni, ma ancora consentire la risoluzione del nome intuitivo e utile come ci si aspetterebbe se il mio il codice era nel loro NS ma è stato esplicitamente introdotto dal client del mio codice (cioè non voglio iniettare il mio codice volenti o nolenti, ma solo su richiesta esplicita)?

un altro pensiero: Forse quello che sarebbe soddisfare la mia sopra criterea sarebbe se ho avuto il seguente:

using namespace X { 
    code here... 
} 

Dove ho potuto mettere un tale costrutto ovunque, anche in un colpo di testa, e non avrei dovuto essere preoccupato di trascinare X nel codice del mio client, ma avrei la stessa libertà di scrivere il mio codice sorgente come farei se fossi nello spazio dei nomi di root.

+5

Per fare l'avvocato del diavolo. N: free() significherebbe più per me se avessi iniziato a leggere il tuo codice sorgente in una posizione casuale. Soprattutto se ho visto X: libero usato un paio di righe dopo.Mi rende più facile leggere il tuo codice senza i tuoi commenti/documentazione –

+0

Per me, ciò sembra controproducente. In tal caso, si dovrebbero semplicemente mettere tutti i nomi nello spazio dei nomi globale e assicurarsi che siano davvero espliciti - "free_a_thing()," create_a_thing() "ecc., Poiché sembra come accade quando si deve mettere Per me, l'idea delle funzioni di sovraccarico è che il compilatore risolve il problema corretto in base all'utilizzo: un'inferenza intelligente da ciò che si scrive. Albero t; Bear b; Can c; free (t) ; free (b); free (c); perché è peggio di free_a_tree (t), free_a_can (c), ... ???! – Mordachai

+1

Perché non 'eliminare m_thing'? – GManNickG

risposta

6

Non vedo per evitare di qualificare lo spazio dei nomi dello scope free() qui, ma si dovrebbe notare che questo non è lo stesso di un "percorso assoluto". Per prima cosa, se si hanno nidificato spazi dei nomi, è sufficiente fare riferimento al più interno:

namespace N1 { 
    namespace N2 { 
    namespace N3 { 
     void free(THING * thing); 

     class C { 
     public: 
     free() { 
      N3::free(m_Thing); // no need to do N1::N2:: 
     } 
     }; 
    } 
    } 
} 

[EDIT] in risposta alla domanda modificato. Ancora una volta, non vedo alcun modo per farlo nello scenario esatto che descrivi. Tuttavia, non sembra essere un approccio C++ idiomatico - il modo più comune per fare lo stesso è fornire la propria classe wrapper per ITEMIDLIST che gestisce tutte le allocazioni, in stile RAII ed espone l'handle originale (ad esempio tramite gli operatori di conversione la ATL, o una funzione membro esplicita come c_str() se si desidera maggiore sicurezza). Ciò eliminerebbe del tutto la necessità del tuo free e, per qualsiasi altra funzione gratuita che potresti desiderare, dato che controlli il tipo di wrapper e lo spazio dei nomi in cui si trova, puoi usare ADL come al solito.

[EDIT # 2]. Questa parte della domanda:

consentire la risoluzione del nome intuitivo e utile come ci si aspetterebbe se il mio codice fosse in loro NS, ma esplicitamente introdotto da parte del cliente del mio codice (cioè non voglio iniettare il mio codice volente o nolente, ma solo su richiesta esplicita)?

Non è questo esattamente quello che mettendolo in uno spazio dei nomi, e il vostro cliente scrittura using namespace ..., realizzerete?

+0

Woot! Imparo qualcosa di nuovo ogni volta che faccio domande qui. Grazie Pavel.Se non lo hai già fatto, darei un'occhiata alla mia domanda aggiornata, e vedere se hai qualche idea sul problema più grande a portata di mano (introduci in modo selettivo funzioni polimorfiche nel codice cliente e la possibilità di farlo per le mie classi, anche nelle mie intestazioni, senza codice client inquinante) – Mordachai

+0

Ho aggiornato la risposta. –

+0

Grazie ancora. E per quanto riguarda la modifica n. 2 - sì, questo è quello che voglio. So che inserire il mio codice nel proprio NS raggiunge questo. Ma l'inconveniente di usare il mio NS è che la ricerca di koenig fallisce (è quello che è ADL?) Per la ricerca di una classe membro-a-libera-funzione, come da mia domanda originale. Quindi, come fornire i vantaggi di costringerli a includere esplicitamente il mio NS, pur non avendo gli svantaggi del mal di testa di risoluzione dei nomi nel mio codice? – Mordachai

5

Sono disponibili due opzioni:

  1. completamente qualificano nome della funzione.
  2. Lasciare il lavoro Koenig lookup (se THING appartiene allo spazio dei nomi N).

sceglierei il primo con un nome di funzione così popolare come free.

L'opzione alternativa è utilizzare m_thing->Release() o qualcosa del genere.

+0

Mi chiedevo se Koenig Lookup avrebbe effettivamente fatto il lavoro, non hai il rischio di 'MyClass :: free' nascondendo' N :: free' durante la ricerca? –

+0

Solo se 'MyClass :: free' ha lo stesso numero di argomenti di' N :: free'. In tal caso non si nasconderà, la chiamata sarà ambigua e riceverai un messaggio di errore. –

+0

La ricerca di Koenig è esattamente quello che voglio. Sfortunatamente, come spesso accade per me, THING NON ​​è definito nel namespace N. È nello spazio dei nomi globale, fornito più spesso da Microsoft (ma a volte anche da un altro venditore di librerie), e perché è un C-struct o calvo puntatore, sto cercando di creare una classe wrapper per gestire la vita dell'oggetto sottostante. Quindi sembra che io possa creare un libero (ITEMIDLIST *) per esempio, ma solo se lo metto nel namespace globale sarà trovato. E come indica MattM, anche allora sarà nascosto dal membro della classe: Devo usare :: free (cosa) :( – Mordachai

0

Yuu potrebbe scrivere ::free(m_thing);. Questo chiamerà la funzione globale free(). Tuttavia, se hai un'altra funzione free() al di fuori dello spazio dei nomi N, ti troverai nei guai.Modifica i nomi di alcune delle tue funzioni o utilizza il nome della funzione con namespace esplicito.

+0

-1 libero è in lo spazio dei nomi N. Sono riluttante a mettere free (THING) nel NS globale per evitare l'iniezione di polimorfismo a funzione libera nel codice del mio client senza un certo livello di controllo (diciamo, costringendoli a dire "usando namespace XYX" o "using XYZ :: free". A questo punto, so che vogliono davvero liberare (THIN G) da iniettare nella loro unità di compilazione, ragionevolmente probabile che non ne saranno sorpresi. – Mordachai

0

Vorrei utilizzare un approccio completamente diverso a quello che stai cercando di realizzare. Penso che tu abbia bisogno di un po 'di ridisegno. Rimuovi i globali e crea una fabbrica/fabbrica astratta che verrà fabbricata (crea un'istanza della classe) e distruggila con il distruttore. In realtà, quello sarebbe l'idioma RAII.

+0

È un buon idioma. Ma come il codice MFC, sto cercando di estendere l'ambiente per consentire l'interazione tra il sistema operativo raw definito THING e una classe di helper C intelligente (nel mio esempio). COSA è già stato creato in vari modi dal sistema operativo. E deve essere distrutto utilizzando una meccanica definita dal sistema operativo. Voglio solo una classe wrapper che nasconda questi dettagli e renda l'uso di THING naturale e molto improbabile-per-perdere. – Mordachai

+0

Cos'è COSA esattamente? – Partial

+0

Ho esteso la mia domanda per spero di chiarire cosa sto cercando di realizzare, e anche con un secondo esempio più concreto (COSA è un ELENCO ITEM *) – Mordachai

0

Mi sembra che ci sia un problema di progettazione qui.

Trovo strano avere lo stesso identificatore utilizzato sia come metodo free che come metodo di classe, in realtà è fonte di confusione. Io cerco di ridurre al minimo il verificarsi, per quanto possibile, anche se ammetto che accade per swap ...

cerco di non qualificare i nomi all'interno del metodo, ma qui ovviamente si corre il rischio di hiding, dove MyClass::free nasconde il metodo N::free, che non partecipa mai alla ricerca ... prova a trovare la risoluzione dell'ambito ma non riesce a trovare il passaggio esatto.

per lo swap ho semplicemente usare:

class MyClass 
{ 
    void swap(MyClass& rhs) 
    { 
    using std::swap;   // Because int, etc... are not in std 
    swap(m_foo, rhs.m_foo); 
    swap(m_bar, rhs.m_bar); 
    } 
}; 

inline void swap(MyClass& lhs, MyClass& rhs) { lhs.swap(rhs); } 

Perciò assicurarsi che il metodo giusto sarà incluso nella ricerca, ed evitare di cadere di nuovo su un metodo 'globale' se del caso.

Preferisco questo approccio alla denominazione esplicita e di solito bulloni using e typedef dichiarazione nella parte superiore del metodo in modo che il codice effettivo non è gonfio.

+0

Sì, il problema è che THING è davvero un'entità in linguaggio C, e non proprio un C++. Sto cercando di fornire un wrapper per il C-thing scadente e di far funzionare il mio wrapper con le regole del C++ per consentire una grande facilità d'uso senza introdurre sorprese per i miei clienti e senza dover scrivere codice fragile nelle intestazioni. – Mordachai

+0

Il wrapping C in C++ è spesso disordinato, è comunque molto lodevole da parte tua, quindi ti auguro buona fortuna! Auguro a tutti gli altri sviluppatori di preoccuparsi così tanto dei loro clienti! –