2010-02-07 18 views
6

Amo il modello in stile Haskell.Stile di corrispondenza del modello in C++?

ho il mio codice C++ come segue:

ObjectPtr ptr; 
if(ptr.isType<Foo>()) { // isType returns a bool 
    Ptr<Foo> p = ptr.convertAs<Foo>(); // convertAs returns a Ptr<Foo> 
    ...... 
} 
if(ptr.isType<Bar>()) { 
    Ptr<Bar> p = ptr.convertAs<Bar>(); 
    ...... 
} 

Ora, ci sono tutte le macro che posso fare definire per semplificare questo? Ho riflettuto su questo per un po ', ma non posso semplificarlo ulteriormente.

Grazie!

+2

Immagino che manchi '()' dopo 'isType'. – AndiDog

+0

Buona chiamata. +1 sul commento. – anon

+0

In Mach7, il codice sarà come segue:

 Match(*ptr) { Case(Foo) return match0.foo_member(); Case(Bar) return match0.bar_member(); Otherwise() return 0; } EndMatch 
Vedi https://github.com/solodon4/Mach7 e la mia risposta su Mach7 sotto – solodon

risposta

2

Sto assumendo che il modello Ptr ha il concetto di un puntatore NULL.

ObjectPtr ptr; 
if(Ptr<Foo> p = ptr.convertAs<Foo>()) { // convertAs returns a NULL pointer if the conversion can't be done. 
    ...... 
} 
if(Ptr<Bar> p = ptr.convertAs<Bar>()) { 
    ...... 
} 

Anche se, come altri hanno notato, l'accensione tipo di solito è un segno che stai facendo qualcosa di sbagliato in C++. Dovresti invece considerare l'utilizzo di funzioni virtuali.

+0

Questo non fa che sollevare un'altra domanda: "come scrivere convertAs() per farlo?" –

+0

@Neil: Non proprio: il suo codice originale presuppone l'esistenza di un convertAs. Questo presumibilmente userebbe un dynamic_cast, che produce un puntatore nullo se non può convertire un puntatore al tipo di destinazione. Pertanto, è molto probabile che sia già presente o abbastanza facile da implementare se non presente. –

+2

La parte migliore è, in C++ 0x è possibile sostituire 'Ptr ' con 'auto'. – Omnifarious

7

dynamic_cast sembrerebbe di fare ciò che si vuole

struct A { 
    virtual ~A() {} 
}; 

struct B : struct A { ... }; 
struct C : struct A { ... }; 

A * a = new C; 

if (C * c = dynamic_cast<C*>(a)) { 
    c->someCfunc(); 
} 
else if (B * b = dynamic_cast<B*>(a)) { 
    b->someBfunc(); 
} 
else { 
    throw "Don't know that type"; 
} 
+0

+1 per lo sforzo. Tuttavia, sto usando i puntatori intelligenti. – anon

+0

Quindi esegui una trasmissione dinamica sul puntatore contenuto, ad esempio, se stai usando std :: auto_ptr(), fallo sul risultato della chiamata get(). –

7

amo Haskell modello di stile di corrispondenza.

Quindi scrivi il tuo programma in Haskell.

Quello che stai cercando di fare è un passaggio su un tipo. Questa è una cosa comune che le persone fanno se vogliono evitare le funzioni virtuali. Ora, questi ultimi sono una pietra miliare di ciò che OO in C++ è tutto. Se vuoi evitarli, perché programmi in C++?


Per quanto riguarda il motivo per cui questo è malvista: Immaginate di avere un sacco di codice come questo

if(ptr.isType<Foo>()) ... 
if(ptr.isType<Bar>()) ... 

spalmato su tutto il codice e poi arriva qualcuno e aggiunge Baz ai possibili tipi che ptr forza rappresentare. Ora stai cercando attraverso una grande base di codice, cercando di trovare tutti quei posti in cui hai cambiato tipo, e cercando di scoprire quali sono necessari per aggiungere Baz a.

E, come Murphy ce l'ha, proprio quando hai finito, arriva anche Foz da aggiungere come tipo. (O, pensando ancora una volta, se Murphy ha il suo modo si insinua nella prima avete avuto la possibilità troppo completa aggiungendo Baz.)

+2

Controllo di memoria a basso livello di C. Costruttori e distruttori di oggetti sullo stack <- questo è sorprendente, poiché fornisce puntatori intelligenti, RAII, ...; libreria di modelli standard (qualcuna di queste usa funzioni virtuali?). 1 assumendo che la risposta fosse una domanda genuina, non un troll. – anon

+1

Certamente non era una domanda genuina, ma non era nemmeno un troll. Era una domanda retorica. Per essere chiari: _Non dovresti farlo in C++ _. Questo è ciò per cui sono state inventate le funzioni virtuali; mettono tutto il codice relativo agli oggetti del tipo 'Foo' in' Foo' stesso, e tutto ciò che riguarda 'Baz' in 'Baz'.Questo non è sempre il meglio che puoi ottenere (testimonia, ad esempio, la motivazione dietro il pattern del visitatore), ma è l'impostazione predefinita da cui iniziare in C++. – sbi

+0

'' Ora, questi ultimi sono una pietra miliare di ciò che è OO in C++ .' Allora perché dovremmo usare la metaprogrammazione del modello quando fondamentalmente FP e non OO affatto, dovremmo tutti andare ad Haskel invece di usare l'STL, potenziare o utilizzare i nostri modelli ... _rolleyes_ Inoltre solo qualcuno completamente ignorante del C++ farebbe una domanda del genere, [come questo ragazzo (link al pdf)] (http://www.stroustrup.com/OpenPatternMatching.pdf) totalmente incompetenti! – Trinidad

2

un think questa macro fa esattamente ciò che si vuole:

#define DYN_IF(dest_type, dest_ptr, src_ptr)         \ 
    if((src_ptr).isType<dest_type>())          \ 
     if(int dest_type##dest_ptr = 1)          \ 
     for(Ptr<dest_type> dest_ptr = (src_ptr).convertAs<dest_type>();  \ 
      dest_type##dest_ptr;            \ 
      dest_type##dest_ptr=0)           

Usage:

ObjectPtr ptr; 
DYN_IF(Foo, foo_ptr, ptr) { 
    // foo_ptr is Ptr<Foo> 
} 
DYN_IF(Bar, bar_ptr, ptr) // Works without braces too for single statement 
    // bar_ptr is Ptr<Bar> 

io non consiglierei questo genere di cose in codice che è fatto per essere letto da qualcun altro , ma dal momento che hai citato la parola "macro" ...

Inoltre, non vorrei far finta che questo abbia qualcosa a che fare con la corrispondenza dei pattern nello stile Haskell/OCaml. Controlla Scala se vuoi un linguaggio con semantica simile a C++ (beh, sorta di) e true pattern matching.

5

Il tentativo di simulare uno stile di corrispondenza del modello in C++ utilizzando RTTI è un'idea semplice, ma è destinato ad avere delle imperfezioni, perché ci sono alcune differenze significative tra i costruttori di tipi di stile di Haskell e Standard ML e le sottoclassi C++. (Nota: qui di seguito, io uso la sintassi standard ML perché sono più a suo agio con esso.)

  • in Haskell e ML standard, pattern matching può legarsi valori nidificati alle variabili del modello per voi (ad esempio, il modello a::b::c::ds lega il i primi tre elementi dell'elenco a a, b e c e il resto dell'elenco a ds). In C++, dovrai comunque scavare attorno alle strutture nidificate, a meno che tu o qualcun altro non abbia creato macro più complicate di quelle che sono state proposte qui.
  • In Haskell e Standard ML, una dichiarazione di tipo datatype del costruttore come datatype 'a option = NONE | SOME of 'a definisce un nuovo tipo: 'a option. I costruttori NONE e SOME non sono tipi, sono valori con i tipi 'a option e 'a -> 'a option, rispettivamente. In C++, quando si definiscono sottoclassi come Foo e Bar per simulare i costruttori di tipi, si ottengono nuovi tipi.
  • In Haskell e Standard ML, i costruttori come SOME sono funzioni di prima classe che costruiscono valori del tipo di dati a cui appartengono. Ad esempio, map SOME ha il tipo 'a list -> 'a option list. In C++, utilizzando sottoclassi per simulare i costruttori di tipi, non si ottiene questa capacità.
  • In Haskell e Standard ML, i tipi di dati sono chiusi, quindi nessuno può aggiungere altri costruttori di tipi senza modificare la dichiarazione originale e il compilatore può verificare in fase di compilazione che la corrispondenza del modello gestisca tutti i casi. In C++, devi fare il possibile per limitare chi può sottoclasse la tua classe base.

Alla fine, ottieni abbastanza benefici dall'abbinamento simulato del modello rispetto all'uso del polimorfismo C++ in un modo più tipico? Usare le macro per rendere la corrispondenza simulata del pattern leggermente più concisa (mentre la offuscate per tutti gli altri che leggono il vostro codice) vale la pena?

4

Abbiamo collaborato alla creazione di una libreria di pattern matching per C++ che consente di eseguire la corrispondenza dei pattern e l'analisi del tipo in modo molto efficiente. La libreria, denominata Mach7, è stata rilasciata con licenza BSD ed è disponibile su GitHub: https://github.com/solodon4/Mach7. Puoi trovare video, poster, diapositive, documenti e il codice sorgente lì. Attualmente supporta GCC 4.4+, Clang 3.4+ e Visual C++ 2010+. Sentiti libero di porre domande sulla biblioteca inviando un problema GitHub al suo repository.

Problemi correlati