2010-01-26 10 views
10

mi c'è voluto molto tempo per rendersi conto di quanto sia importante e sottili con le variabili che:C++ modelli specifici dovuti alla progettazione del linguaggio

1) esiste nello stack

2) hanno i loro distruttori chiamato quando cadono fuori di scopo

sono.

Queste due cose permettono cose come:

A) RAII

B) refcounted GC

Abbastanza interessante, (1) & (2) non sono disponibili in lingue "minori" come C/Assemblea; né in linguaggi "superiori" come Ruby/Python/Java (dal momento che GC previene la distruzione prevedibile degli oggetti).

Sono curioso - quali altre tecniche sapete che sono molto specifiche del linguaggio C++, a causa delle scelte di progettazione linguistica.

Grazie!

Modifica: Se la risposta è "questo funziona in C++ & questo altro langauge", va bene lo stesso. Le cose che voglio imparare sono simili a:

Scegliendo di non avere alcune caratteristiche (come GC), otteniamo altre funzionalità (come RAII + distruzione prevedibile di oggetti). In quali aree del C++, scegliendo NON hanno caratteristiche che altri linguaggi "di livello superiore" hanno, C++ riesce a ottenere schemi che quelle lingue superiori non possono esprimere.

+0

non credo che C++ ha GC, C# fa, naturalmente, ma che è una bestia diversa. – Hogan

+1

Sì, la gestione delle risorse tramite RAII (con o senza conteggio ref) non è in realtà GC, è molto più generale. –

+0

Più si verifica in momenti predeterminati, GC non è deterministico. – Hogan

risposta

3

Mi piacciono molto i corsi di tratto. Non esattamente specifico del C++ (altri linguaggi come Scala li hanno), ma consente di adattare gli oggetti, in sostanza per specificare un insieme di operazioni che un tipo dovrebbe supportare. Immagina di volere un "hasher", nel senso di tr1::hash. l'hash è definito per alcuni tipi, ma non per altri. Come puoi creare una classe che ha un hash definito per tutti i tipi che vuoi? È possibile dichiarare una classe come ad esempio:

template < typename T> 
struct hashing_tratis 
{ 
    typedef std::tr1::hash<T> hashing_type; 
}; 

che è, ci si aspetta una classe che ha il hasing_type corretta definito per essere utilizzato. Tuttavia, hash non è definito, per esempio, per myType, in modo da poter scrivere:

template <> 
struct hashing_traits<myType> 
{ 
    typedef class_that_knows_how_to_hash_myType hashing_type; 
}; 

In questo modo, si supponga che avete bisogno di un modo per hash qualsiasi tipo che si usa nel programma (compreso myType).Puoi scrivere un hasher "universale" creando un tratto hasing:

template <typename T> 
struct something { 
    typename hashing_traits<T>::hashing_type hasher; 
    .... // here hasher is defined for all your relevant types, and knows how to hash them 
+0

Le lingue che supportano la riflessione possono farlo senza l'ingombrante classe dei tratti. Chiedono solo alla classe se supporta un'interfaccia e la chiama. Lo stesso è anche possibile in lingue con supporto di funzioni del primo ordine. – jmucchiello

+1

jmucchiello: Beh, non esattamente. Cosa succede, ad esempio, quando chiedi una classe o un'interfaccia che questo oggetto non implementa? Devi "cercare" un adattamento. La parametrizzazione di tipo in C++ consente questa costruzione di tratto con un'interfaccia omogenea. –

2

Beh, questo può essere fatto nel linguaggio di programmazione D, così come i due si parla, ma i modelli potenti abbastanza per funzionare essenzialmente come compilazione tempo anatra digitando sono abbastanza utili. Ho spesso l'impressione che una buona parte del dibattito tra i linguaggi statici e quelli dinamici possa essere risolta con un sistema di template sufficientemente buono, così che anche se i tipi devono essere risolti staticamente in fase di compilazione, non è necessario che siano noti al momento dello sviluppo. C++ ha aperto la strada a quest'idea (almeno tra le lingue tradizionali). D lo fa ancora di più.

Con modelli e compilare in tempo duck typing si ottiene il meglio dei due mondi: La flessibilità di non dover specificare i tipi al momento lo sviluppo di una funzione o classe e la sicurezza e le prestazioni di conoscerle al momento della compilazione.

+0

La domanda riguarda specificamente C++. –

+1

@Neil: sto solo sottolineando che non è ** completamente ** specifico, anche se C++ è la lingua più popolare in cui questa tecnica è disponibile. – dsimcha

1

Beh, quasi tutti i modelli di progettazione '' hanno un forte legame con C++. Dovresti piuttosto pensare che non abbiano NIENTE a che fare con "design appropriato" e "best practice" e TUTTO quello che fa con bizzarri difetti del C++ e considerare attentamente il reale bisogno di ognuno di essi invece di complicare il tuo codice. Soprattutto perché molti dei modelli di design sono cerotti per risolvere problemi creati da altri modelli di design. Questo si applica dieci volte di più quando si usa una lingua diversa dal C++, perché il C++ ha un enorme numero di problemi che nessun altro linguaggio fa.

Ad esempio, singleton è l'esempio più ovvio di questo.La vera ragione di ciò è il modo in cui C++ ha inizializzato in modo molto scarso l'inizializzazione statica. Senza questo, in realtà devi sapere cosa stai facendo per far funzionare correttamente l'inizializzazione, e la maggior parte delle persone non lo fa. Per la maggior parte degli usi, il sovraccarico extra di singleton va bene, ma si dovrebbe tenere presente che ha un sovraccarico e non ha alcun uso nella maggior parte delle lingue. Crea anche ulteriori problemi in ogni singola implementazione che ho visto.

Lo stesso vale per il modello di bridge, posso vedere utilizzando singleton, ma semplicemente non c'è motivo di utilizzare mai il bridge pattern. Si riduce a 'sai cosa stai facendo?'. Se è così, non è necessario il pattern del bridge. In caso contrario, dovresti probabilmente saperne di più prima di provare a risolvere il problema chiedendoti di utilizzare il pattern del bridge. Soprattutto, non è davvero utile in tutte le lingue oltre al C++. Il punto è separare l'implementazione e l'interfaccia, che in più moderni linguaggi OO è già fatto perché hanno moduli appropriati di qualche tipo. In C++, è meglio utilizzare pure interfacce virtuali per casi come questo (e non pensate che questo abbia prestazioni peggiori, ha prestazioni molto migliori rispetto al bridge pattern combinato con i template).

Ma questo è il problema con i modelli di progettazione; non è affatto una metodologia, solo un po 'di cose a caso. Le cose a caso la maggior parte delle persone che le difendono non capiscono veramente, la maggior parte delle quali non ha alcun luogo in cui si chiami qualcosa con la parola design in esse. Non sono strumenti di progettazione, ma soluzioni specifiche a vari problemi. Molti dei quali sono evitabili.

Ironia della sorte, l'API java è piena di bridge e un'ampia varietà di altri modelli di progettazione che sono totalmente inutili in Java. L'API Java è facile da usare la maggior parte del tempo, ma la gerarchia eccessivamente complicata può essere molto complicata con cui lavorare.

+0

Perché una buona implementazione di modelli di bridge con modelli ha prestazioni peggiori rispetto a una basata su vtables? Inoltre, i bridge non sono solo una questione di moduli, nascondere la complessità con un approccio basato su livelli è il suo valore principale indipendentemente dalla lingua. –

+0

informatica. –

+1

Un ulteriore livello di riferimento indiretto non influisce sulle prestazioni tanto quanto aiuta le buone abitudini GRASP. E non stai rispondendo alla domanda. –

2
  • sempre fare il chiamante responsabile della memoria di allocazione/deallocazione, il che significa che il codice che sarebbe simile a questa in Java/C#:

    MyClass doSomething(MyClass someInstance); 
    

    assomiglia a questo in C/C++:

  • Utilizzando distruttori di chiudere i socket, file-maniglie, ecc
+1

Oppure 'boost :: shared_ptr doSomething (const MyClass & someInstance);' –

+1

Se si desidera che FORCE il chiamante sia responsabile per alloc/free, è necessario utilizzare i riferimenti: void doSomething (const myclass & in_param, myclass & out_param); Non puoi chiamare doSomething assegnando out_param da qualche parte e facendolo passare. – jmucchiello

+0

In realtà, è una pratica migliore assumersi la piena responsabilità di tutte le allocazioni/dealloc che il tuo codice dovrebbe utilizzare. Puoi farlo (1) usando puntatori intelligenti che libereranno le risorse allocate quando non sono più utilizzate e (2) restituendo un oggetto tramite copia e rendendo libera la copia delle sue risorse quando vengono distrutte. –

0

Supporto per alcune lingue multiple dispatch (con più comunemente lo double dispatch) come fa il C++, è possibile farlo in modo statico o dinamico.
Con esso è possibile consentire alle istanze di interagire l'una con l'altra senza ancora conoscere o testare per il loro tipo concreto.

+0

Direi che quasi TUTTI i linguaggi (tranne alcuni come CLOS che lo supportano in modo nativo) lo supportano come fa il C++ - devi codificarlo da solo. –

+0

C# 4.0 ce l'ha fatta! – Hogan

+0

@Hogan: Oh, devo cercarlo. Solo dinamicamente, credo? –

1

C'è l'idioma pImpl. Funziona in modo simile al pattern Bridge.

E il Expression Template, che ritarda le valutazioni di espressione del C++.

Queste due opere anche esplorare questa domanda: More C++ Idioms e Effective C++ (Scott Meyers, 2005)

Problemi correlati