Che cosa fa C ++ meglio di D?


135

Recentemente sto imparando D e sto iniziando a familiarizzare con la lingua. So cosa offre, non so ancora come usare tutto, e non so molto sui modi di dire D e così via, ma sto imparando.

Mi piace D. È un bel linguaggio, essendo, in qualche modo, un enorme aggiornamento di C, e fatto bene. Nessuna delle caratteristiche sembra "imbullonata", ma in realtà abbastanza ben pensata e ben progettata.

Sentirai spesso che D è ciò che avrebbe dovuto essere il C ++ (lascio la domanda se questo sia vero per ognuno e per tutti decidere se stessi al fine di evitare inutili guerre di fiamma). Ho anche sentito da diversi programmatori C ++ che apprezzano D molto più di C ++.

Io stesso, mentre conosco C, non posso dire di conoscere C ++. Mi piacerebbe sapere da qualcuno che conosce sia C ++ che D se pensano che ci sia qualcosa che C ++ fa meglio di D come linguaggio (che significa non il solito "ha più librerie di terze parti" o "ci sono più risorse" o " esistono più lavori che richiedono C ++ che D ").

D è stato progettato da alcuni programmatori C ++ molto abili ( Walter Bright e Andrei Alexandrescu , con l'aiuto della comunità D) per risolvere molti dei problemi che il C ++ aveva, ma c'era qualcosa che in realtà non è migliorato dopo tutto? Qualcosa che gli è mancato? Qualcosa che pensi non sia una soluzione migliore?

Inoltre, tieni presente che sto parlando di D 2.0 , non di D 1.0 .


15
Mi sono assicurato che la comunità D lo vedesse poiché sono sicuro che ci sono molti più sviluppatori C ++ rispetto agli sviluppatori D qui intorno. In questo modo avrai risposte più interessanti o almeno varie.
Klaim,

7
Inoltre, D2 è stato progettato da Walter Bright ma anche con Alexandrescu. Potresti voler risolvere questo problema nella tua domanda.
Klaim,

2
@Klaim: c'era (ed è tuttora) molto coinvolgimento della comunità nella libreria D e standard.
Michal Minich,

28
@Anto Come linguaggio, C ++ è molto meglio di D nel farti odiare la tua vita da programmatore.
Arlen,

6
@jokoon: In realtà sì, con pochissimo lavoro: digitalmars.com/d/2.0/interfaceToC.html
Anto

Risposte:


124

La maggior parte delle cose che C ++ "fa" meglio di D sono meta-cose: C ++ ha compilatori migliori, strumenti migliori, librerie più mature, più collegamenti, più esperti, più tutorial ecc. Fondamentalmente ha più e meglio di tutte le cose esterne che tu si aspetterebbe da una lingua più matura. Questo è indiscutibile.

Per quanto riguarda il linguaggio stesso, secondo me ci sono alcune cose che C ++ fa meglio di D. Probabilmente c'è di più, ma eccone alcuni che posso elencare in cima alla mia testa:

Il C ++ ha un sistema di tipi meglio
concepito Al momento ci sono alcuni problemi con il sistema di tipi in D, che sembrano essere svantaggi nella progettazione. Ad esempio, è attualmente impossibile copiare una const const in una struct non const se la struct contiene riferimenti a oggetti di classe o puntatori a causa della transitività di const e del modo in cui i costruttori postblit lavorano sui tipi di valore. Andrei dice che sa come risolvere questo problema, ma non ha fornito alcun dettaglio. Il problema è certamente risolvibile (l'introduzione di costruttori di copie in stile C ++ sarebbe una soluzione), ma al momento è un grosso problema nel linguaggio.

Un altro problema che mi ha infastidito è la mancanza di const logica (cioè no mutablecome in C ++). Questo è ottimo per scrivere codice thread-safe, ma rende difficile (impossibile?) Un'inizializzazione pigra all'interno di oggetti const (si pensi a una funzione const 'get' che costruisce e memorizza nella cache il valore restituito alla prima chiamata).

Infine, dato questi problemi esistenti, sono preoccupato per come il resto del sistema tipo ( pure, shared, etc.) interagirà con tutto il resto nella lingua una volta che vengono messe a frutto. La libreria standard (Phobos) attualmente utilizza pochissimo il sistema di tipo avanzato di D, quindi penso che sia ragionevole chiedersi se resisterà allo stress. Sono scettico, ma ottimista.

Si noti che C ++ ha alcune verruche di sistema di tipo (ad esempio const non transitive, che richiedono iteratore const_iterator) che lo rendono abbastanza brutto, ma mentre il sistema di tipi di C ++ è un po 'sbagliato in alcune parti, non ti impedisce di fare il lavoro come D a volte lo fa.

Modifica: per chiarire, credo che C ++ abbia un sistema di tipi meglio studiato, non necessariamente migliore, se questo ha senso. In sostanza, in DI ritengono che vi sia un rischio nell'uso di tutti gli aspetti del suo sistema di tipi che non è presente in C ++.

D a volte è un po 'troppo conveniente
Una critica che si sente spesso parlare di C ++ è che ti nasconde alcuni problemi di basso livello, ad esempio incarichi semplici come a = b;potrebbe fare molte cose come chiamare operatori di conversione, chiamare operatori di sovraccarico ecc., Che possono essere difficile da vedere dal codice. Ad alcune persone piace questo, ad altre no. In entrambi i casi, in D è peggio (meglio?) A causa di cose come opDispatch, @property, opApply, lazyche hanno il potenziale per cambiare il codice cercando innocente in cose che non ti aspetti.

Non penso che questo sia un grosso problema personalmente, ma alcuni potrebbero trovare scoraggiante.

D richiede la raccolta dei rifiuti
Questo potrebbe essere visto come controverso perché è possibile eseguire D senza GC. Tuttavia, solo perché è possibile non significa che sia pratico. Senza un GC, perdi molte funzionalità di D e usare la libreria standard sarebbe come camminare in un campo minato (chissà quali funzioni allocano la memoria?). Personalmente, penso che sia totalmente impraticabile usare D senza un GC, e se non sei un fan dei GC (come me), questo può essere abbastanza scoraggiante.

Definizioni di array ingenui in D allocare memoria
Questo è un mio peeve pet:

int[3] a = [1, 2, 3]; // in D, this allocates then copies
int a[3] = {1, 2, 3}; // in C++, this doesn't allocate

Apparentemente, per evitare l'allocazione in D, devi fare:

static const int[3] staticA = [1, 2, 3]; // in data segment
int[3] a = staticA; // non-allocating copy

Queste piccole allocazioni "alle tue spalle" sono buoni esempi dei miei due precedenti punti.

Modifica: si noti che questo è un problema noto su cui si sta lavorando.
Modifica: ora risolto. Non viene effettuata alcuna allocazione.

Conclusione
Mi sono concentrato sugli aspetti negativi di D vs C ++ perché questa è la domanda posta, ma per favore non vedere questo post come un'affermazione che C ++ è migliore di D. Potrei facilmente creare un post più ampio di luoghi in cui D è migliore di C ++. Sta a te decidere quale utilizzare.


Ho guardato D alcuni anni fa (prima della 2.0). La raccolta dei rifiuti non era davvero necessaria allora: era lì per impostazione predefinita, ma potresti optare per il codice di basso livello. La cosa che pensavo fosse sbagliata in questo è che non riuscivo a trovare alcun modo per riaccedere. Ad esempio, in un contenitore basato su albero, il codice della libreria può gestire la memoria per i nodi dell'albero stesso. L'albero nel suo insieme può ancora essere collezionabile, con IIRC un distruttore che raccoglie tutti quei nodi. Ma anche gli oggetti a cui fanno riferimento i dati in quel contenitore dovrebbero essere collezionabili - ci dovrebbe essere un gancio per contrassegnare tutti gli elementi di dati nella struttura ad albero per il GC.
Steve314

3
Puoi ancora disabilitare il GC per il codice di basso livello - Peter sta dicendo che la lingua attualmente dipende molto da esso. Inoltre, puoi dire al GC di scansionare intervalli al di fuori del suo heap gestito usando la sua API: GC.addRange da core.memory .
Vladimir Panteleev il

+1 per indicare che la libreria standard D viene raccolta in modo inutile e che il codice GC-off non è un'interoperabilità continua. Non è qualcosa a cui ci ho pensato, ma sembra un grosso ostacolo da superare.
Masonk,

132

Quando mi sono unito allo sviluppo D ero nella posizione peculiare di essere una delle persone che sanno di più che c'è da sapere sul C ++. Ora sono nella posizione ancora più peculiare di essere anche una delle persone che sanno di più che c'è da sapere su D. Non lo dico per appropriarsi dei diritti di credito o vantarsi, quanto per notare che sono in una curiosità posizione privilegiata per rispondere a questa domanda. Lo stesso vale per Walter.

In generale, chiedere cosa fa il C ++ (e con questo intendo il C ++ 2011) meglio di D è tanto contraddittorio quanto la domanda: "Se paghi un professionista per pulire la tua casa, quali sono i posti che lasceranno più sporco di prima? " Qualunque sia il valore che C ++ potrebbe fare e che D non potrebbe fare, è sempre stato un pollice dolente per me e Walter, quindi quasi per definizione non c'è nulla che C ++ possa mai fare che non sia alla portata di D.

Una cosa che è raramente compresa nella progettazione del linguaggio (perché poche persone hanno la fortuna di fare effettivamente alcuni) è che ci sono molti meno errori non forzati di quanto possa sembrare. Molti di noi utenti di lingue guardano un costrutto o un altro e dicono: "Ew! È così sbagliato! Che cosa stavano pensando?" Il fatto è che i casi più imbarazzanti in una lingua sono il risultato di alcune decisioni fondamentali che sono tutte sane e desiderabili ma che sono fondamentalmente in competizione o in contraddizione tra loro (ad esempio modularità ed efficienza, terseness e controllo ecc.).

Con tutto ciò in mente, enumererò alcune cose che mi vengono in mente, e per ognuna spiegherò come la scelta di D deriva dal desiderio di soddisfare qualche altra carta di livello superiore.

  1. D presuppone che tutti gli oggetti siano mobili mediante copia bit a bit. Ciò lascia una minoranza di progetti in C ++, in particolare quelli che utilizzano puntatori interni, ovvero una classe contenente puntatori al suo interno. (Qualsiasi progetto di questo tipo può essere tradotto senza costi di efficienza n o trascurabili in D, ma ci sarebbe uno sforzo di traduzione.) Abbiamo preso questa decisione per semplificare notevolmente la lingua, rendere la copia degli oggetti più efficiente con nessun intervento dell'utente minimo o minimo, ed evitare l'intero morass di costruzione della copia e i riferimenti al valore sono presenti del tutto.

  2. D non consente tipi di genere ambigui (che non possono decidere se sono tipi di valore o di riferimento). Tali progetti sono evitati all'unanimità in C ++ e quasi sempre sbagliati, ma alcuni di essi sono tecnicamente corretti. Abbiamo fatto questa scelta perché non consente il codice per lo più errato e solo una piccola parte di codice corretto che può essere riprogettato. Crediamo che sia un buon compromesso.

  3. D non consente gerarchie multi-root. Un poster precedente qui era molto entusiasta di questo particolare argomento, ma questo è terreno ben calpestato e non vi è alcun vantaggio tangibile delle gerarchie senza radice rispetto alle gerarchie che hanno tutte una radice comune.

  4. In D non puoi lanciare ad esempio un int. Devi lanciare un oggetto che eredita Throwable. In nessun caso lo stato delle cose è migliore in D, ma, beh, è ​​una cosa che C ++ può fare che D non può fare.

  5. In C ++ l'unità di incapsulamento è una classe. In D è un modulo (cioè file). Walter ha preso questa decisione per due motivi: mappare naturalmente l'incapsulamento alla semantica della protezione del file system e ovviare alla necessità di "amico". Questa scelta si integra molto bene nel progetto di modularità generale di D. Sarebbe possibile cambiare le cose per assomigliare di più al C ++, ma ciò forzerebbe le cose; Le scelte dell'ambito di incapsulamento di C ++ sono valide solo per la progettazione fisica di C ++.

Potrebbero esserci una o due cose più piccole, ma nel complesso dovrebbe essere così.


6
@DeadMG: affinché funzioni in C ++, l'oggetto da spostare avrebbe bisogno di un puntatore indietro sull'oggetto che punta ad esso (in modo che possa aggiornarsi durante la costruzione della copia). In tal caso, in D è possibile utilizzare il costruttore postblit per aggiornare comunque il puntatore. Per favore, non discutere contro D se ne hai solo una conoscenza passeggera.
Peter Alexander,

13
@Peter: dovrebbe essere un riferimento anche se la sua durata è strettamente basata sull'ambito? Dovrei sprecare il sovraccarico di allocarlo in modo dinamico e le spese generali di indirizzamento e cache e raccolta perché voglio alias? Inoltre, spero che il collezionista possa raccoglierlo in modo deterministico, per una semantica equivalente. Questo è chiaramente non avere il controllo.
DeadMG

3
@sbi: l'esistenza di una classe superiore non influisce affatto sulle tue scelte. Nel reticolo del tipo di classe, c'è sempre una parte superiore e una parte inferiore. Il fondo è esplicito solo in alcune lingue . La parte superiore (ovvero oggetto ecc.) È esplicita in più lingue. Questi tipi esistono sempre nel concetto; quando sono anche accessibili, offrono semplicemente alcune funzionalità extra all'utente della lingua senza creare problemi.
Andrei Alexandrescu,

6
@quant_dev: sarai felice di sapere che esiste già un progetto GSoC in una buona forma incentrato sull'algebra lineare ad alte prestazioni che utilizza BLAS. Fornisce inoltre implementazioni "ingenue" delle primitive appropriate per scopi di test e benchmarking. Per rispondere alla tua seconda domanda, Java imposta la barra piuttosto bassa per il confronto delle librerie numeriche. Avrà sempre il problema di superare una barriera JNI per accedere alle librerie di algebra ad alte prestazioni e la sintassi sarà cattiva perché Java non ha un sovraccarico da parte dell'operatore.
Andrei Alexandrescu,

4
@PeterAlexander: DeadMG è proprio sul punto. "Non dovresti usare i puntatori per i tipi di valore" è chiaramente ignaro del fatto che i puntatori in qualsiasi lingua sono generalmente usati con i tipi di valore (ti aspetti davvero di vedere un Object*così ampiamente usato come un int*?) E D sembra ignorare completamente la penalità di prestazione, o sostenendo che non esiste. Questo è ovviamente falso: la cache cache è abbastanza evidente in molti casi, quindi C ++ avrà sempre quel vantaggio di flessibilità rispetto a D.
Mehrdad,

65

Penso che avrai difficoltà a trovare molto in D che è oggettivamentepeggio di C ++. La maggior parte dei problemi con D in cui si potrebbe obiettivamente dire che è peggio sono entrambi problemi di qualità dell'implementazione (che sono generalmente dovuti a quanto sono giovani il linguaggio e l'implementazione e sono stati risolti a una velocità vertiginosa di recente), o sono problemi con una mancanza di librerie di terze parti (che arriveranno con il tempo). Il linguaggio in sé è generalmente migliore del C ++, e i casi in cui il C ++, come linguaggio, è meglio, generalmente si troveranno dove c'è un compromesso in cui C ++ è andato in un modo e D è andato in un altro, o in cui qualcuno ha ragioni soggettive del perché pensa che uno sia migliore di un altro. Ma il numero di ragioni obiettive per cui il C ++, come linguaggio, è migliore, è probabilmente tra pochi.

In effetti, devo davvero rovinare il mio cervello per trovare le ragioni per cui il C ++, come linguaggio, è migliore di D. Ciò che viene in mente generalmente è una questione di compromessi.

  1. Poiché la const di D è transitiva e poiché il linguaggio è immutabile , ha garanzie molto più forti di quelle di C ++ const, il che significa che D non ha e non può avere mutable. Non può avere una const logica . Quindi, ottieni un enorme guadagno con il sistema const di D, ma in alcune situazioni, non puoi semplicemente usarlo constcome avresti in C ++.

  2. D ha un solo operatore di cast, mentre C ++ ne ha 4 (5 se si conta l'operatore di cast C). Ciò semplifica la gestione dei cast in D nel caso generale, ma è problematico quando si vogliono effettivamente le complicazioni / i benefici extra che i const_castsuoi fratelli offrono. Ma D è in realtà abbastanza potente da poter usare i template per implementare i cast di C ++, quindi se li vuoi davvero, puoi averli (e potrebbero persino finire nella libreria standard di D ad un certo punto).

  3. D ha un numero di cast impliciti molto inferiore rispetto a C ++ ed è molto più probabile che dichiari che due funzioni sono in conflitto l'una con l'altra (costringendoti ad essere più specifico su quale delle funzioni intendi - con i cast o fornendo il percorso completo del modulo ). A volte, può essere fastidioso, ma impedisce tutti i tipi di problemi di dirottamento delle funzioni . Sai che stai davvero chiamando la funzione che intendi fare.

  4. Il sistema di moduli di D è molto più pulito di #includes di C ++ (per non parlare, molto più veloce nella compilazione), ma manca di qualsiasi tipo di spaziatura dei nomi oltre i moduli stessi. Quindi, se si desidera uno spazio dei nomi all'interno di un modulo, è necessario seguire la rotta Java e utilizzare le funzioni statiche su una classe o struttura. Funziona, ma se vuoi davvero lo spazio dei nomi, ovviamente non è pulito come il vero spazio dei nomi. Per la maggior parte delle situazioni, tuttavia, lo spazio dei nomi che i moduli stessi ti forniscono è abbondante (e piuttosto sofisticato quando si tratta di cose come i conflitti in realtà).

  5. Come Java e C #, D ha ereditarietà singola anziché ereditaria multipla, ma a differenza di Java e C #, offre alcuni modi fantastici per ottenere lo stesso effetto senza tutti i problemi che ha l'ereditarietà multipla di C ++ (e l'ereditarietà multipla di C ++ può diventare molto disordinata a volte). Non solo D ha interfacce , ma ha anche mixin di stringhe , mixin di template e alias . Quindi, il risultato finale è probabilmente più potente e non ha tutti i problemi della eredità multipla di C ++.

  6. Simile a C #, D separa le strutture e le classi . Le classi sono tipi di riferimento che hanno ereditarietà e sono derivati ​​da Object, mentre le strutture sono tipi di valore senza ereditarietà. Questa separazione può essere sia buona che cattiva. Elimina il classico problema di slicing in C ++ e aiuta a separare tipi che sono davvero tipi di valore da quelli che dovrebbero essere polimorfici, ma all'inizio, almeno, la distinzione potrebbe essere fastidiosa per un programmatore C ++. In definitiva, ci sono una serie di vantaggi, ma ti costringe a gestire i tuoi tipi in modo leggermente diverso.

  7. Le funzioni membro delle classi sono polimorfiche per impostazione predefinita. Non puoi dichiararli non virtuali . Spetta al compilatore decidere se possono essere (il che in realtà è solo il caso se sono definitivi e non hanno la precedenza su una funzione da una classe base). Quindi, questo potrebbe essere un problema di prestazioni in alcuni casi. Tuttavia, se davvero non hai bisogno del polimorfismo, allora tutto ciò che devi fare è usare le strutture , e non è un problema.

  8. D ha un cestino per la spazzatura incorporato . Molti dal C ++ considererebbero che questo è un aspetto negativo e, a dire il vero, al momento, la sua implementazione potrebbe richiedere un lavoro serio. Sta migliorando, ma sicuramente non è ancora alla pari con il Garbage Collector di Java. Tuttavia, questo è mitigato da due fattori. Uno, se stai utilizzando principalmente le strutture e altri tipi di dati nello stack, non è un grosso problema. Se il tuo programma non sta allocando e deallocando costantemente cose sull'heap, andrà bene. E due, puoi saltare il Garbage Collector se vuoi e usare solo C malloce free. Ci sono alcune funzionalità linguistiche (come la suddivisione in array) che dovrai evitare o fare attenzione, e alcune delle librerie standard non sono realmente utilizzabili senza almeno un uso del GC (in particolare l'elaborazione delle stringhe), ma puoi scrivere in D senza usare il Garbage Collector se lo vuoi davvero. La cosa intelligente da fare è probabilmente usarlo generalmente e quindi evitarlo laddove la profilazione mostra che sta causando problemi per il codice critico per le prestazioni, ma puoi evitarlo completamente se lo desideri. E la qualità dell'implementazione del GC migliorerà nel tempo, eliminando molte delle preoccupazioni che l'uso di un GC può causare. Quindi, in definitiva, il GC non sarà un grosso problema e, a differenza di Java, puoi evitarlo se lo desideri.

Probabilmente ce ne sono anche altri, ma è quello che posso trovare in questo momento. E se noterai, sono tutti compromessi. D ha scelto di fare alcune cose in modo diverso rispetto al C ++, che presentano vantaggi evidenti rispetto al modo in cui C ++ le fa, ma presentano anche alcuni svantaggi. La cosa migliore dipende da cosa stai facendo, e in molti casi probabilmente sembrerà solo peggio all'inizio e quindi non avrai problemi con esso una volta che ti sarai abituato. Semmai, i problemi in D saranno generalmente nuovi causati da cose nuove che altre lingue non hanno mai fatto prima o che non hanno fatto esattamente come ha fatto D. Nel complesso, D ha imparato molto bene dagli errori di C ++.

E D, come linguaggio, migliora rispetto al C ++ in così tanti modi che penso che sia generalmente il caso che D sia oggettivamente migliore.

  1. D ha una compilazione condizionale . Questa è una delle caratteristiche che mi mancano spesso quando sto programmando in C ++. Se C ++ lo aggiungesse, allora C ++ migliorerebbe a passi da gigante quando si tratta di cose come i template.

  2. D ha una riflessione in fase di compilazione .

  3. Le variabili sono thread-local per impostazione predefinita, ma possono essere sharedse lo si desidera. Ciò rende la gestione dei thread molto più pulita rispetto al C ++. Hai il controllo completo. È possibile utilizzare immutablee passare messaggi per comunicare tra i thread oppure è possibile creare variabili sharede farlo in modo C ++ con mutex e variabili di condizione. Anche questo è migliorato su C ++ con l'introduzione di sincronizzato (simile a C # e Java). Quindi, la situazione di threading di D è di gran lunga migliore di quella di C ++.

  4. I modelli di D sono molto più potenti dei modelli di C ++, permettendoti di fare molto di più, molto più facilmente. E con l'aggiunta dei vincoli del modello, i messaggi di errore sono molto meglio di quanto siano in C ++. D rende i modelli molto potenti e utilizzabili. Non è un caso che l'autore di Modern C ++ Design sia uno dei principali collaboratori di D. Trovo che i modelli C ++ siano seriamente carenti rispetto ai modelli D, e può essere molto frustrante a volte durante la programmazione in C ++.

  5. D ha una programmazione di contratto integrata .

  6. D ha un framework di unit test integrato.

  7. D ha il supporto integrato per Unicode con string(UTF-8), wstring(UTF-16) e dstring(UTF-32). Semplifica la gestione di Unicode. E se vuoi semplicemente usare stringe generalmente non preoccuparti dell'unicode, puoi farlo - anche se una certa comprensione delle basi di Unicode aiuta con alcune delle funzioni standard della libreria.

  8. Il sovraccarico dell'operatore di D è molto più bello di quello del C ++, consentendo di utilizzare una funzione per sovraccaricare più operatori contemporaneamente. Un primo esempio di ciò è quando è necessario sovraccaricare gli operatori aritmetici di base e le loro implementazioni sono identiche, salvo per l'operatore. I mixin di stringhe lo rendono un gioco da ragazzi, permettendoti di avere una semplice definizione di funzione per tutti.

  9. Le matrici di D sono di gran lunga migliori delle matrici di C ++. Non solo sono un tipo appropriato con una lunghezza, ma possono essere aggiunti e ridimensionati. Concatenarli è facile. E soprattutto, hanno affettare . E questo è un grande vantaggio per un'elaborazione efficiente dell'array. Le stringhe sono array di caratteri in D, e non è un problema (in effetti è fantastico!), Perché gli array di D sono così potenti.

Potrei andare avanti all'infinito. Molti dei miglioramenti che D offre sono piccole cose (come usare thisper nomi di costruttori o non consentire se istruzioni o corpi di loop in cui un punto e virgola è il loro intero corpo), ma alcuni di loro sono piuttosto grandi, e quando li sommi tutti insieme rende l' esperienza di programmazione molto migliore. C ++ 0x aggiunge alcune delle caratteristiche che D ha quale C ++ mancava (ad esempio autoe lambdas), ma anche con tutti i suoi miglioramenti, non ci sarà ancora molto che sia obiettivamente migliore di C ++ come linguaggio di D.

Non c'è dubbio che ci sono molte ragioni soggettive per apprezzare l'una sull'altra e l'immaturità relativa dell'implementazione di D può essere un problema a volte (anche se negli ultimi tempi è migliorata molto rapidamente, specialmente da quando i repository sono stati spostati su Github ) e la mancanza di librerie di terze parti può sicuramente essere un problema (sebbene il fatto che D possa facilmente chiamare funzioni C - e, in misura minore, funzioni C ++ - mitiga definitivamente il problema). Ma questi sono problemi di qualità piuttosto che problemi con il linguaggio stesso. E poiché la qualità dei problemi di implementazione viene risolta, diventerà molto più piacevole usare D.

Quindi, suppongo che la risposta breve a questa domanda sia "molto piccola". D, come linguaggio, è generalmente superiore al C ++.


2
I linguaggi di Garbage Collection usano 2-5 volte più memoria rispetto ai non GC (secondo il discorso di Alexandrescu su YT), quindi questo è sicuramente un problema se quello (uso mem) è il collo di bottiglia.
NoSenseEtAl

9

RAII e utilizzo della memoria dello stack

D 2.0 non consente a RAII di verificarsi nello stack perché ha rimosso il valore della scopeparola chiave nell'allocazione di istanze di classe nello stack.

Non è possibile eseguire l'ereditarietà del tipo di valore in D, così efficacemente che ti costringe a fare un'allocazione di heap per qualsiasi forma di RAII.
Cioè, a meno che tu non lo usi emplace, ma è molto doloroso da usare, poiché devi allocare la memoria a mano. (Devo ancora trovarlo pratico da usare emplacein D.)


6

Il C ++ è molto più efficace nel forzarti ad essere prolisso. Questo potrebbe essere migliore o peggiore ai tuoi occhi, a seconda che ti piaccia l'inferenza o la verbosità.

Confronta la memoization di runtime in C ++ :

template <typename ReturnType, typename... Args>
function<ReturnType (Args...)> memoize(function<ReturnType (Args...)> func)
{
    map<tuple<Args...>, ReturnType> cache;
    return ([=](Args... args) mutable {
            tuple<Args...> t(args...);
            return cache.find(t) == cache.end()
                ? cache[t] : cache[t] = func(args...);
    });
}

con la stessa cosa in D:

auto memoize(F)(F func)
{
    alias ParameterTypeTuple!F Args;
    ReturnType!F[Tuple!Args] cache;
    return (Args args)
    {
        auto key = tuple(args);
        return key in cache ? cache[key] : (cache[key] = func(args));
    };
}

Si noti, ad esempio, la verbosità extra con template <typename ReturnType, typename... Args>versus (F), Args...versus Args, args...versus args, ecc. Nel
bene e nel male, C ++ è più prolisso.

Certo, potresti farlo anche in D:

template memoize(Return, Args...)
{
    Return delegate(Args) memoize(Return delegate(Args) func)
    {
        Return[Tuple!Args] cache;
        return delegate(Args args)
        {
            auto key = tuple(args);
            return key in cache ? cache[key] : (cache[key] = func(args));
        };
    }
}

e sembrerebbero quasi identici, ma ciò richiederebbe a delegate, mentre l'originale accettava qualsiasi oggetto richiamabile. (La versione C ++ 0x richiede unstd::function oggetto, quindi in entrambi i casi è più dettagliata e restrittiva nei suoi input ... il che potrebbe essere buono se ti piace la verbosità, cattivo se non lo fai.)


2

Non so molto di D, ma molti, molti programmatori di C ++ che conosco non mi piacciono molto, e personalmente devo essere d'accordo: non mi piace l'aspetto di D e non ne prenderò uno più vicino.

Per capire perché D non sta guadagnando più trazione, devi iniziare capendo cosa attira le persone al C ++. In una parola, il motivo numero uno è il controllo. Quando programmi in C ++, hai il controllo completo sul tuo programma. Vuoi sostituire la libreria standard? Puoi. Vuoi eseguire cast di puntatori non sicuri? Puoi. Vuoi violare la correttezza const? Puoi. Vuoi sostituire l'allocatore di memoria? Puoi. Vuoi copiare la memoria grezza senza tener conto del tipo? Se vuoi davvero. Vuoi ereditare da più implementazioni? È il tuo funerale. Diavolo, puoi persino ottenere librerie di raccolta rifiuti, come il collezionista Boehm. Quindi hai problemi come le prestazioni, che seguono da vicino il controllo: più controllo ha un programmatore, più può ottimizzare il suo programma.

Ecco alcune cose che ho visto quando ho fatto una piccola ricerca e ho parlato con un paio di persone che l'hanno provato:

Gerarchia di tipi unificata. Gli utenti C ++ usano l'ereditarietà molto raramente, la maggior parte dei programmatori C ++ preferisce la composizione e i tipi dovrebbero essere collegati tramite ereditarietà solo se c'è una ragione molto valida per farlo. Il concetto di oggetto viola fortemente questo principio collegando ogni tipo. Inoltre, sta violando uno dei principi di base del C ++: usi solo ciò che desideri. Non avere la scelta di ereditare da Object, e i costi che ne derivano, sono fortemente contrari a ciò che C ++ rappresenta come linguaggio in termini di controllo del programmatore sul suo programma.

Ho sentito parlare di problemi con funzioni e delegati. Apparentemente, D ha sia funzioni che delegati come tipi di funzioni richiamabili di runtime, e non sono uguali ma sono intercambiabili o ... qualcosa? Il mio amico ha avuto alcuni problemi con loro. Questo è sicuramente un downgrade dal C ++, che ha appena fatto std::functione il gioco è fatto.

Quindi hai compatibilità. D non è particolarmente compatibile con C ++. Voglio dire, nessun linguaggio è compatibile con C ++, ammettiamolo, tranne C ++ / CLI che è una specie di imbroglio, ma come barriera all'ingresso, deve essere menzionato.

Quindi, ci sono altre cose. Ad esempio, basta leggere la voce di Wikipedia.

import std.metastrings;
pragma(msg, Format!("7! = %s", fact_7));
pragma(msg, Format!("9! = %s", fact_9));

printfè una delle funzioni più sicure mai inventate, nella stessa famiglia di grossi problemi come quelli getsdella vecchia libreria C Standard. Se lo cerchi su Stack Overflow, troverai molte, molte domande relative al suo uso improprio. Fondamentalmente, printfè una violazione di DRY- stai dando il tipo nella stringa di formato e poi lo dai di nuovo quando gli dai un argomento. Una violazione di DRY in cui se si sbaglia, allora accadono cose molto brutte, se si cambia un typedef da un numero intero a 16 bit in uno a 32 bit. Inoltre, non è affatto estensibile, immagina cosa accadrebbe se tutti inventassero i propri identificatori di formato. Gli iostreams del C ++ possono essere lenti e la loro scelta dell'operatore potrebbe non essere la migliore, e la loro interfaccia potrebbe usare il lavoro, ma sono fondamentalmente garantiti per essere sicuri, e DRY non viene violato e possono essere prontamente estesi. Questo non è qualcosa di cui si possa dire printf.

Nessuna eredità multipla. Questo non è molto il modo C ++. I programmatori C ++ si aspettano di avere il controllo completo sul proprio programma e il linguaggio che impone ciò da cui non è possibile ereditare costituisce una violazione di tale principio. Inoltre, rende l'ereditarietà (ancora più) fragile, perché se si modifica un tipo da un'interfaccia a una classe perché si desidera fornire un'implementazione predefinita o qualcosa del genere, improvvisamente tutto il codice dell'utente viene interrotto. Non è una buona cosa.

Un altro esempio è stringe wstring. In C ++ è già abbastanza doloroso const char*doverli convertire tra di loro, e questa libreria supporta Unicode, e questa vecchia libreria C usa solo e deve scrivere versioni diverse della stessa funzione a seconda del tipo di argomento stringa che si desidera. In particolare, le intestazioni di Windows, ad esempio, hanno alcune macro estremamente irritanti per far fronte al problema che spesso può interferire con il tuo codice. L'aggiunta dstringal mix non farà che peggiorare le cose, poiché ora invece di due tipi di stringa, devi gestirne tre. Avere più di un tipo di stringa aumenterà i problemi di manutenzione e introdurrà un codice ripetitivo relativo alle stringhe.

Scott Meyers scrive:

D è un linguaggio di programmazione creato per aiutare i programmatori ad affrontare le sfide dello sviluppo di software moderno. Lo fa promuovendo moduli interconnessi attraverso interfacce precise, una federazione di paradigmi di programmazione strettamente integrati, isolamento dei thread forzato dal linguaggio, sicurezza di tipo modulare, un modello di memoria efficiente e altro ancora.

L'isolamento del thread forzato dal linguaggio non è un vantaggio. I programmatori C ++ si aspettano il pieno controllo dei loro programmi e il linguaggio che impone qualcosa non è assolutamente quello che il medico ha ordinato.

Parlerò anche della manipolazione di stringhe in fase di compilazione. D ha la capacità di interpretare il codice D in fase di compilazione. Questo non è un vantaggio. Considera gli enormi mal di testa causati dal preprocessore relativamente limitato di C, ben noto a tutti i programmatori veterani C ++, e poi immagina quanto gravemente questa funzionalità verrà abusata. La capacità di creare codice D in fase di compilazione è eccezionale, ma dovrebbe essere semantica , non sintattica.

Inoltre, puoi aspettarti un certo riflesso. D ha la garbage collection, che i programmatori C ++ assoceranno a linguaggi come Java e C # che si oppongono abbastanza direttamente ad esso nelle filosofie, e le somiglianze sintattiche ne faranno pensare anche a loro. Questo non è necessariamente oggettivamente giustificabile, ma è qualcosa che certamente dovrebbe essere notato.

Fondamentalmente, non offre così tanto che i programmatori C ++ non possono già fare. Forse è più facile scrivere un metaprogramma fattoriale in D, ma può già scrivere metaprograms fattoriali in C ++. Forse in D puoi scrivere un ray-tracer in fase di compilazione, ma nessuno vuole farlo comunque. Rispetto alle violazioni fondamentali della filosofia C ++, ciò che puoi fare in D non è particolarmente notevole.

Anche se queste cose sono solo problemi in superficie, allora sono abbastanza sicuro che il fatto che in superficie D non assomigli affatto al C ++ è probabilmente una buona ragione per cui molti programmatori C ++ non stanno migrando verso D. Forse D ha bisogno di fare un lavoro migliore pubblicizzando se stesso.


9
@DeadMG: è errato al 100% e manca il punto di dire che questo è sicuramente un downgrade dal C ++, che ha appena fatto std::functione il gioco è fatto. Perché? Perché anche tu, ad esempio, hai dei puntatori a funzione. È esattamente la stessa cosa nelle "funzioni" di D: D sono i puntatori di funzione, e i "delegati" di D sono gli stessi di quelli di C ++ std::function(tranne che sono integrati). Non c'è "downgrade" da nessuna parte - e c'è una corrispondenza 1: 1 tra di loro, quindi non dovrebbe essere affatto confuso se hai familiarità con C ++.
Mehrdad,

10
@Mark Trapp: devo ammettere che non capisco bene la tua posizione sull'argomento - i commenti non devono essere usati per, sai, commentare una risposta?
klickverbot,

6
@Mark Trapp: il mio punto è che la maggior parte dei commenti qui non erano obsoleti (la meta discussione che hai collegato si applica specificamente ai suggerimenti che sono già stati incorporati nel post originale), ma ha sottolineato inesattezze fattuali nel post, che sono ancora presenti .
klickverbot,

7
Una nota sul formato: la funzione di formato D è typesafe (risolve i problemi di sicurezza / overflow) e non viola DRY poiché la stringa di formato specifica solo come devono essere formattati gli argomenti, non i loro tipi. Questo è possibile grazie alle variabili di tipo typesafe di D. Pertanto, tale obiezione almeno non è completamente valida.
Justin W

17
@Mark: Qualunque sia la politica attuale che li riguarda, trovo stupido e ostacolante il fatto che le discussioni sui commenti vengano eliminate . Penso che questa risposta abbia avuto ampie discussioni (che ora mi interessano), ma non ne sono sicuro e non ho modo di scoprirlo. Quella stanza a cui hai collegato contiene oltre 10.000 messaggi e non ho alcuna possibilità di trovare una discussione che mi sembra di aver avuto luogo, ma di cui non ricordo il contenuto. Le discussioni su questa risposta appartengono qui, a questa risposta , e non ad alcune chat room, in cui potrebbero essere mescolate in discussioni su sesso, droga e rock'n'roll.
sabato

1

Una cosa che apprezzo in C ++ è la capacità di documentare un argomento di funzione o restituire un valore come riferimento C ++ anziché come puntatore, implicando quindi l'assunzione di un non nullvalore.

Versione D:

class A { int i; }

int foo(A a) {
    return a.i; // Will crash if a is null
}

int main() {
    A bar = null;
    // Do something, forgetting to set bar in all
    // branches until finally ending up at:
    return foo(bar);
}

Versione C ++:

class A { int i; };

int foo(A& a) {
    return a.i; // Will probably not crash since
                // C++ references are less likely
                // to be null.
}

int main() {
    A* bar = null;
    // Do something, forgetting to set bar in all
    // branches until finally ending up at:
    // Hm.. I have to dereference the bar-pointer
    // here, otherwise it wont compile.  Lets add
    // a check for null before.
    if (bar)
        return foo(*bar);
    return 0;
}

Per essere onesti, puoi avvicinarti molto al C ++ trasformandolo Ain una D structe contrassegnando il foo()-argument come a ref(le classi sono tipi di riferimento e le strutture sono tipi di valore in D, simile a C #).

Credo che ci sia un piano per creare un NonNullablemodello per le classi come costrutto di libreria D standard. Anche così mi piace la brevità di solo Type&rispetto a NonNullable(Type), e preferirei non annullabile come predefinito (rendering qualcosa di simile Typee Nullable(Type)). Ma è troppo tardi per cambiarlo per D e ora sto andando fuori tema.


3
Sia gli argomenti delle funzioni che i valori restituiti in D possono essere contrassegnati con refper ottenere lo stesso effetto di C ++ &. L'unica grande differenza è che refnon ci vorrà un temporaneo anche se lo è const.
Jonathan M Davis

Mi piace il modo in cui la restituzione di riferimenti a variabili locali è vietata in D, non ne ero a conoscenza finché non ho letto il tuo commento. Ma puoi comunque restituire un riferimento null non locale senza pensarci in un modo in cui l'operatore di dereference C ti farà pensare.
lumor

Stai confondendo le cose. Le classi sono sempre riferimenti e questo è separato dal rif. I riferimenti in D sono come riferimenti in Java. Sono puntatori gestiti. Passare o tornare da ref è come passare o tornare con & in C ++. Passare un riferimento di classe tramite ref è come passare un puntatore in C ++ con & (es. A * &). Le lezioni non vanno in pila. Sì, NonNullable renderebbe possibile avere un riferimento di classe che era garantito essere non nullo, ma completamente separato da ref. Quello che stai cercando di fare nel codice C ++ non funziona in D perché le classi non vanno nello stack. Le strutture lo fanno.
Jonathan M Davis il

1
Quindi sì, essere in grado di avere un riferimento di classe che non è nulla sarebbe bello, ma C ++ riesce a fare quello che stai mostrando perché consente alle classi di essere nello stack e consente di dereferenziare i puntatori. E mentre puoi dereferenziare i puntatori in D, le classi sono riferimenti, non puntatori, quindi non puoi dereferenziarli. Dato che non puoi metterli in pila e non puoi dereferenziarli, non c'è modo in D di avere una classe che non può essere nulla. Si tratta di una perdita, ma nonnullable lo risolverà, ei guadagni dalla separazione di strutture e classi sono generalmente maggiore comunque.
Jonathan M Davis il

2
I riferimenti C ++ non possono essere nulli dallo standard del linguaggio (dicendo "probabilmente non sarà nullo" non è corretto poiché non può essere nullo). Vorrei che ci fosse un modo per non consentire null come valore valido per una classe.
jsternberg,

1

La cosa più importante che C ++ "fa meglio" di D è l' interfaccia con le librerie legacy . Vari motori 3D, OpenCL e simili. Poiché D è nuovo, ha una quantità molto più piccola di librerie diverse tra cui scegliere.

Un'altra importante differenza tra il C ++ e il D è che il C ++ ha più fornitori indipendenti dal punto di vista finanziario, ma dal 2014 il D ha praticamente solo uno , il team di 2 uomini che lo ha creato. È interessante notare che il "principio della seconda fonte" che afferma che i progetti non dovrebbero mai dipendere da tecnologia, componenti, che hanno un solo fornitore, sembra valere anche per il software.

Per fare un confronto, la prima versione dell'interprete Ruby è stata scritta dall'inventore di Ruby, lo Yukihiro Matsumoto, ma l'interprete Ruby mainstream dell'era 2014 è stata praticamente scritta da zero da altre persone. Pertanto, Ruby può essere visto come una lingua che ha più di un fornitore finanziariamente indipendente. D, d'altra parte, può essere una tecnologia fantastica, ma dipende dai pochi sviluppatori che la sviluppano.

La storia di Java mostra che anche se una tecnologia, in questo caso, Java, ha un buon, ma unico, finanziatore, c'è un grande rischio che la tecnologia venga essenzialmente abbandonata, indipendentemente dall'enorme base di utenti aziendali. Una citazione della Apache Software Foundation , in cui la CE sta per "Comitato esecutivo":

Oracle ha fornito alla CE una richiesta di specifica Java SE 7 e una licenza che sono contraddittorie, limitano fortemente la distribuzione di implementazioni indipendenti della specifica e, soprattutto, vietano la distribuzione di implementazioni indipendenti open source della specifica.

Come nota storica, si può dire che le applet Java hanno accelerato l'hardware della tela 3D anni prima che lo HTML5 WebGL venisse sviluppato. Il problema della velocità di avvio delle applet Java avrebbe potuto essere risolto se Java fosse stato un progetto di comunità, ma i dirigenti dell'unico finanziatore di Java, Sun Microsystems, non hanno trovato abbastanza importante che l'implementazione Java fosse corretta. Il risultato finale: tela HTML5 di più fornitori come "replica di un uomo povero" dei framework della GUI Java (Swing, ecc.). È interessante notare che sul lato server il linguaggio di programmazione Python ha gli stessi vantaggi che Java ha promesso: scrivere una volta, eseguire su ogni server, indipendentemente dall'hardware, a condizione che l'applicazione Python non sia compilata sul codice macchina. Il Python è vecchio / giovane quanto lo è Java, ma a differenza di Java, è '

Sommario:

Nel valutare la tecnologia per l'uso in produzione, le proprietà più importanti delle tecnologie sono le persone, che la sviluppano, il processo sociale, dove avviene lo sviluppo e il numero di team di sviluppo finanziariamente indipendenti.

Se sia più vantaggioso utilizzare la tecnologia T1 rispetto alla tecnologia T2 dipende dai fornitori delle tecnologie e se la tecnologia T1 consente di risolvere i problemi relativi al progetto in modo più economico rispetto a T2. Ad esempio, se il problema relativo al singolo fornitore fosse ignorato, per i sistemi informatici Java sarebbe una tecnologia "migliore" rispetto al C ++, poiché i binari Java non necessitano di ricompilazione al momento della distribuzione su nuovo hardware e la quantità di lavoro di sviluppo del software relativo alla gestione della memoria è più piccolo per Java rispetto a C ++. I progetti sviluppati da zero, ad esempio progetti che non dipendono da altre librerie, potrebbero essere più economici da sviluppare in D rispetto a C ++, ma, d'altra parte, il C ++ ha più di un fornitore e quindi è meno rischioso a lungo termine . (L'esempio Java, in cui Sun Microsystems era quasi OK,

Una possibile soluzione ad alcune delle limitazioni di C ++

Una delle possibili soluzioni ad alcune delle limitazioni del C ++ consiste nell'utilizzare un modello di progettazione, in cui l'attività iniziale viene suddivisa in pezzi e i pezzi vengono "ordinati" (classificati, raggruppati, divisi, altri-belle-parole-per- la stessa cosa) a 2 classi: controllo delle attività correlate alla logica , in cui i modelli di accesso alla memoria non consentono la localizzazione; attività simili all'elaborazione del segnale , in cui la località è facilmente raggiungibile. Le attività "simili" all'elaborazione del segnale possono essere implementate come un insieme di programmi console relativamente semplicistici. Le attività relative alla logica di controllo sono collocate tutte in un singolo script scritto in Ruby o Python o in altro modo, in cui il comfort di sviluppo del software ha una priorità più alta della velocità.

Per evitare costose inizializzazioni del processo del sistema operativo e copia dei dati tra i processi del sistema operativo, il set di piccole applicazioni console C ++ potrebbe essere implementato come un singolo programma C ++ che viene avviato come "servlet" da Ruby / Python / ecc. script. Il rubino / Python / ecc. lo script arresta il servlet prima di uscire. Comunicazione tra "servlet" e Ruby / Python / ecc. lo script si svolge su una pipe denominata Linux o su un meccanismo simile.

Se l'attività iniziale non si presta a essere facilmente suddivisa in pezzi che possono essere classificati nelle 2 classi sopra menzionate, una cosa da provare potrebbe essere quella di riformulare il problema in modo che l'attività iniziale cambi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.