Quali sono i problemi di portare C ++ - const in un linguaggio?


17

Sono interessato all'idea del C ++ - come constnon quella particolare esecuzione (come il casting away const).

Prendiamo ad esempio C # - manca C ++ - come const, e la ragione è la solita - persone e tempo. Qui inoltre sembra che il team di C # abbia esaminato l'esecuzione di C ++ di constmarketing CLR e ne abbia avuto abbastanza a questo punto (vedere perché non esiste un metodo membro const in c # e parametro const ; grazie Svick). Sia se ci muoviamo oltre, qualcos'altro?

C'è qualcosa di più profondo? Prendiamo ad esempio la multi-ereditarietà: di solito è vista come difficile da comprendere (per l'utente) e quindi non aggiunta al linguaggio (come il problema del diamante).

C'è qualcosa nella NATURA di constche pongono un problema che è meglio per il linguaggio per evitarlo? Qualcosa come decidere se constdovrebbe essere profondo o superficiale (avere un constcontenitore significa che non posso aggiungere nuovo elemento o alterare anche elementi esistenti; cosa succede se gli elementi sono di tipo di riferimento)?

AGGIORNAMENTO : mentre menziono C #, e quindi facendo una prospettiva storica, sono interessato alla natura dei potenziali problemi della constlingua.

Questa non è una sfida delle lingue. Per favore ignora fattori come le tendenze attuali o la popolarità - Sono interessato solo a problemi tecnici - grazie.


3
a parte: l'eredità multipla potrebbe non essere difficile da comprendere, ma la risoluzione del metodo può diventare piuttosto brutta. La risoluzione ingenua in profondità diventa rapidamente poco intuitiva (" problema del diamante "). L' ordine di risoluzione metodo di C3 (pubblicato '96) risolve questo, ma è certamente non è facile da capire. La fuorilegge dell'ereditarietà multipla può quindi spesso sembrare abbastanza ragionevole, ma il vero problema è che Java precede l'algoritmo C3 e C # si orienta dopo Java.
amon

3
@amon Molte persone non pensano davvero che l'ereditarietà multipla sia una caratteristica che vuoi avere in primo luogo, ad esempio i creatori della lingua di programmazione D: è una caratteristica complessa di valore discutibile. È molto difficile da implementare in modo efficiente e i compilatori sono inclini a molti bug nell'attuazione. Quasi tutto il valore dell'MI può essere gestito con una singola eredità unita a interfacce e aggregazione. Ciò che rimane non giustifica il peso dell'implementazione dell'MI. (fonte)
jcora


2
Anche senza l' ereditarietà multipla, puoi già codificare qualsiasi problema 3-SAT come risoluzione del metodo (o più precisamente risoluzione di sovraccarico) in C #, rendendolo NP-completo.
Jörg W Mittag

1
@svick, grazie mille. Dal momento che non avevo alcuna risposta a questa domanda, mi sono preso la libertà di modificarlo pesantemente, per concentrarmi sulla natura del problema, non sui motivi storici, perché sembra che il 99% di essi sia "tempo + persone + pregiudizio anti C ++ + CLR".
Greenoldman,

Risposte:


10

Nota se questo è adatto a te, ma in linguaggi funzionali come Standard ML tutto è immutabile per impostazione predefinita. La mutazione è supportata attraverso un reftipo di erenza generico . Quindi una intvariabile è immutabile e una ref intvariabile è un contenitore mutabile per ints. Fondamentalmente, le variabili sono variabili reali in senso matematico (un valore sconosciuto ma fisso) e refs sono "variabili" in senso imperativo di programmazione - una cella di memoria che può essere scritta e da cui leggere. (Mi piace chiamarli assegnabili .)

Penso che il problema constsia duplice. Innanzitutto, il C ++ manca della garbage collection, necessaria per avere strutture di dati persistenti non banali . const deve essere profondo per avere un senso, ma avere valori completamente immutabili in C ++ è impraticabile.

In secondo luogo, in C ++ è necessario optare constpiuttosto che non. Ma quando ti dimentichi di constqualcosa e in seguito lo risolvi, finirai nella situazione di "avvelenamento da costanti" menzionata nella risposta di @ RobY in cui il constcambiamento si ripercuoterà su tutto il codice. Se constfosse l'impostazione predefinita, non ti ritroveresti ad applicare constretroattivamente. Inoltre, dover aggiungere constovunque aggiunge molto rumore al codice.

Sospetto che i linguaggi tradizionali che seguirono (ad es. Java) furono fortemente modellati dal successo e dal modo di pensare di C e C ++. Caso in questione, anche con la raccolta dei rifiuti, le API della maggior parte delle lingue assumono strutture di dati mutabili. Il fatto che tutto sia mutevole e l'immutabilità è visto come un caso esemplare che parla a lungo della mentalità imperativa dietro le lingue popolari.

EDIT : Dopo aver riflettuto sul commento di Greenoldman mi sono reso conto che constnon riguarda direttamente l'immutabilità dei dati; constcodifica nel tipo di metodo se ha effetti collaterali sull'istanza.

È possibile utilizzare la mutazione per ottenere un comportamento referenzialmente trasparente . Supponiamo di avere una funzione che, quando chiamata, restituisce in successione valori diversi, ad esempio una funzione che legge un singolo carattere da stdin. Potremmo usare cache / memoize i risultati di questa funzione per produrre un flusso di valori referenzialmente trasparente. Lo stream sarebbe un elenco collegato i cui nodi chiameranno la funzione la prima volta che si tenta di recuperare il loro valore, ma quindi memorizzare nella cache il risultato. Quindi, se il stdinproblema persiste Hello, world!, la prima volta che provi a recuperare il valore del primo nodo, ne leggerà uno chare tornerà H. Successivamente continuerà a tornare Hsenza ulteriori chiamate per leggere a char. Allo stesso modo, il secondo nodo avrebbe letto un chardastdinla prima volta che provi a recuperarne il valore, questa volta restituisce ee memorizza nella cache quel risultato.

La cosa interessante qui è che hai trasformato un processo intrinsecamente stato in un oggetto apparentemente senza stato. Tuttavia, è stato necessario mutare lo stato interno dell'oggetto (memorizzando nella cache i risultati) per raggiungere questo obiettivo: la mutazione era un effetto benigno . È impossibile rendere il nostro CharStream constanche se il flusso si comporta come un valore immutabile. Ora immagina che ci sia Streamun'interfaccia con i constmetodi e tutte le tue funzioni si aspettano const Streams. Il tuo CharStreamnon può implementare l'interfaccia!

( EDIT 2: Apparentemente c'è una parola chiave C ++ chiamata mutableche ci permetterebbe di imbrogliare e creareCharStream const . Tuttavia, questa scappatoia distrugge constle garanzie - ora non puoi davvero essere sicuro che qualcosa non muterà attraverso i suoi constmetodi. Suppongo che non sia così male dal momento che è necessario richiedere esplicitamente la scappatoia, ma si fa ancora completamente affidamento sul sistema d'onore.)

In secondo luogo, supponiamo di avere funzioni di ordine elevato, ovvero è possibile passare funzioni come argomenti ad altre funzioni. constness fa parte della firma di una funzione, quindi non si sarebbe in grado di passare le non constfunzioni come argomenti alle funzioni che si aspettano constfunzioni. Applicare ciecamente constqui porterebbe a una perdita di generalità.

Infine, manipolare un constoggetto non garantisce che non stia mutando uno stato esterno (statico o globale) alle tue spalle, quindi constle garanzie non sono così forti come sembrano inizialmente.

Non mi è chiaro che codificare la presenza o l'assenza di effetti collaterali nel sistema dei tipi sia universalmente una buona cosa.


1
+1 grazie. Gli oggetti immutabili sono qualcosa di diverso dalla const C ++ (è più di una vista). Ma diciamo, C ++ avrebbe const di default, e varsu richiesta lascerebbe solo un problema: la profondità?
Greenoldman,

1
@greenoldman Un buon punto. È da un po 'che non uso C ++, quindi ho dimenticato che puoi avere un constriferimento a un non- constoggetto. Anche così, non credo che abbia alcun senso avere un superficiale const. Il punto centrale constè la garanzia che non sarai in grado di modificare l'oggetto attraverso quel riferimento. Non ti è permesso aggirare constcreando un non- constriferimento, quindi perché sarebbe corretto aggirare constmutando i membri dell'oggetto?
Doval

+1 :-) Problemi molto interessanti nel tuo aggiornamento (vivere con lambda non / const e forse un problema più debole con lo stato esterno), devo digerirlo più a lungo. Ad ogni modo, per quanto riguarda il tuo commento, non ho in mente nulla di malvagio - al contrario, sto cercando funzionalità per aumentare la chiarezza del codice (un altro: puntatori non nulli). Stessa categoria qui (almeno è il mio scopo) - Definisco func foo(const String &s)e questo è un segnale in scui non verrà modificato foo.
Greenoldman,

1
@greenoldman Posso sicuramente vedere l'appello e la logica. Tuttavia, non penso che ci sia una vera soluzione al problema se non quella di evitare il più possibile lo stato mutevole (insieme ad altre cose brutte come l' nullereditarietà).
Doval

8

Il problema principale è che i programmatori tendono a non usarlo abbastanza, quindi quando colpiscono un posto dove è richiesto, o un manutentore in seguito vuole correggere la correttezza const, c'è un enorme effetto a catena. Puoi ancora scrivere un codice immutabile perfettamente buono senza const, il tuo compilatore non lo imporrà e avrà un momento più difficile da ottimizzare. Alcune persone preferiscono che il loro compilatore non li aiuti. Non capisco quelle persone, ma ce ne sono molte.

Nei linguaggi funzionali, praticamente tutto è const, ed essere mutevole è la rara eccezione, se è permesso a tutti. Ciò ha diversi vantaggi, come una più facile concorrenza e un ragionamento più semplice sullo stato condiviso, ma ci vuole un po 'per abituarsi se si proviene da una lingua con una cultura mutevole. In altre parole, non volere constè un problema di persone, non tecnico.


+1 grazie. Mentre l'oggetto immutabile è qualcosa di diverso da quello che sto cercando (puoi renderlo immutabile in C # o Java), anche con un approccio immutabile devi decidere se è profondo o superficiale, prendi ad esempio il contenitore di puntatori / riferimenti (puoi cambia il valore degli oggetti su cui puntare). Dopo un giorno sembra che il problema principale sia piuttosto ciò che è impostato come predefinito ed è facilmente risolvibile quando si progetta un nuovo linguaggio.
Greenoldman,

1
+1 per il lolz @ "Alcune persone preferiscono che il loro compilatore non le aiuti. Non capisco quelle persone, ma ce ne sono molte."
Thomas Eding,

6

Vedo gli svantaggi come:

  • "avvelenamento da const" è un problema quando una dichiarazione di const può forzare una dichiarazione precedente o successiva a utilizzare const, e ciò può far sì che la sua precedente delle seguenti dichiarazioni utilizzi const, ecc. E se l'avvelenamento da const scorre in una classe in una libreria di cui non hai il controllo, quindi puoi rimanere bloccato in un brutto posto.

  • non è una garanzia assoluta. Di solito ci sono casi in cui la const protegge solo il riferimento, ma non è in grado di proteggere i valori sottostanti per un motivo o per l'altro. Anche in C, ci sono casi limite che const non può raggiungere, il che indebolisce la promessa che const fornisce.

  • in generale, il sentimento del settore sembra allontanarsi da forti garanzie in fase di compilazione, per quanto confort possano portarti e io. Quindi per il pomeriggio spendo toccando il 66% della mia base di codice a causa di avvelenamento da costanti, alcuni Python guy ha eliminato 10 moduli Python perfettamente funzionanti, ben progettati, testati e perfettamente funzionanti. E non stava nemmeno hackerando quando lo ha fatto.

Quindi forse la domanda è: perché portare avanti una caratteristica potenzialmente controversa di cui anche alcuni esperti sono ambivalenti quando stai cercando di ottenere l'adozione del tuo nuovo linguaggio spettrale?

EDIT: risposta breve, una nuova lingua ha una ripida collina da scalare per l'adozione. I progettisti devono davvero riflettere attentamente su quali funzionalità sono necessarie per ottenere l'adozione. Prendi Go, per esempio. Google ha troncato brutalmente alcune delle più profonde battaglie religiose facendo alcune scelte di design in stile Conan-il-Barbaro, e in realtà penso che il linguaggio sia migliore, da quello che ho visto.


2
Il tuo secondo proiettile non è corretto. In C ++, ci sono tre / quattro modi per dichiarare un riferimento / puntatore, wrt. constness: int *puntatore mutable su valore mutable, int const *puntatore mutable su valore int * constconst , puntatore const su valore mutable, int const * constpuntatore const su valore const.
phresnel

Grazie. Ho chiesto informazioni sulla natura, non sul marketing ("l'industria che si allontana" non è una natura simile al C ++ const), quindi tali "motivi" che ritengo irrilevanti (almeno per questa domanda). A proposito di constavvelenamento - puoi fare un esempio? Perché AFAIK è il vantaggio, si passa qualcosa conste dovrebbe rimanere const.
Greenoldman,

1
Non è il constproblema, ma cambiare il tipo davvero - qualunque cosa tu faccia, sarà sempre un problema. Considera di passare RichStringe poi di realizzare il tuo passaggio migliore String.
Greenoldman,

4
@RobY: Non sono sicuro di cosa / a chi ti stai rivolgendo con quest'ultimo ed i precedenti . Tuttavia: dopo quel pomeriggio, camminando attraverso il codice, avresti dovuto imparare a rifattorizzare la costanza della correttezza dal basso verso l'alto, non dall'alto verso il basso, ovvero iniziare nelle unità più interne e procedere verso l'alto. Forse anche le tue unità non erano abbastanza isolate, invece erano strettamente accoppiate, o sei caduto preda di sistemi interni.
galleria

7
"Const avvelenamento" non è davvero un problema - il vero problema è che il C ++ è impostato su non- const. È troppo facile non le constcose che dovrebbero essere constperché devi optare per esso anziché per esso.
Doval,

0

Ci sono alcuni problemi filosofici nell'aggiungere consta una lingua. Se si dichiara consta una classe, ciò significa che la classe non può cambiare. Ma una classe è un insieme di variabili accoppiate a metodi (o mutatori). Raggiungiamo l'astrazione nascondendo lo stato di una classe dietro una serie di metodi. Se una classe è progettata per nascondere lo stato, allora hai bisogno di mutatori per cambiare quello stato dall'esterno e non lo è constpiù. Quindi è possibile dichiarare solo constalle classi che sono immutabili. Quindi constsembra possibile solo in scenari in cui le classi (oi tipi che vuoi dichiarare const) sono banali ...

Quindi abbiamo bisogno constcomunque? Non la penso così. Penso che puoi facilmente farne a meno constse hai un buon design. Di quali dati hai bisogno e dove, quali metodi sono dati da dati a dati e quali astrazioni ti servono per I / O, rete, vista ecc. Quindi dividi naturalmente moduli e classi che si occupano dello stato e hai metodi e classi immutabili che non necessitano di stato.

Penso che la maggior parte dei problemi sorga con oggetti dati di grandi dimensioni che possono salvare, trasformare e disegnare se stessi e quindi si desidera passare ad un renderer per esempio. Quindi non è possibile copiare il contenuto dei dati, perché è troppo costoso per un grande oggetto-dati. Ma non vuoi neanche che cambi, quindi hai bisogno di qualcosa come constpensi. Lascia che il compilatore capisca i tuoi difetti di progettazione. Tuttavia, se si suddividono tali oggetti solo in un oggetto dati immutabile e si implementano i metodi nel modulo responsabile, è possibile passare (solo) il puntatore e fare le cose che si desidera fare con i dati garantendo allo stesso tempo l'immutabilità.

So che in C ++ è possibile dichiarare alcuni metodi conste non dichiararli su altri metodi, perché sono mutatori. E poi qualche tempo dopo hai bisogno di una cache e quindi un getter muta un oggetto (perché si carica lentamente) e non lo è constpiù. Ma quello era il punto centrale di questa astrazione, giusto? Non sai quale stato viene mutato ad ogni chiamata.


1
"Quindi const sembra possibile solo in scenari in cui le classi (oi tipi che si desidera dichiarare const) sono banali ..." Le strutture di dati persistenti sono immutabili e non banali. Semplicemente non li vedrai in C ++ perché praticamente tutti condividono i loro dati interni tra più istanze e C ++ manca la garbage collection per farlo funzionare. "Penso che puoi facilmente fare a meno se hai un buon design." I valori immutabili rendono il codice molto più semplice da ragionare.
Doval

+1 grazie. Potresti chiarire "dichiarare const a una classe"? Intendevi impostare la classe come const? In caso affermativo, non sto chiedendo questo: const C ++ funziona come una vista, puoi dichiarare una const accanto all'oggetto, non una classe (o qualcosa è cambiato in C ++ 11). Inoltre c'è un problema con il prossimo paragrafo e la parola "facilmente" - c'è una buona domanda su C ++ - const in C # e la quantità di lavoro che comporta è fondamentale - per ogni tipo che intendi mettere const(in senso C ++ ) devi definire l'interfaccia, anche per tutte le proprietà.
Greenoldman,

1
Quando parli di una "classe" intendi effettivamente "istanza di una classe", giusto? Penso che sia una distinzione importante.
svick
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.