Perché 'void *' non è implicitamente cast in C ++?


30

In C, non è necessario eseguire il cast di a void *su qualsiasi altro tipo di puntatore, viene sempre promosso in modo sicuro. Tuttavia, in C ++, non è così. Per esempio,

int *a = malloc(sizeof(int));

funziona in C, ma non in C ++. (Nota: so che non dovresti usare mallocin C ++, o per quello new, e dovresti invece preferire puntatori intelligenti e / o STL; questo è chiesto per pura curiosità) Perché lo standard C ++ non consente questo cast implicito, mentre lo standard C fa?


3
long *a = malloc(sizeof(int));Oops, qualcuno ha dimenticato di cambiare solo un tipo!
Doval

4
@Doval: è ancora facilmente risolvibile usando sizeof(*a)invece.
WolfPack88

3
Credo che il punto di @ ratchetfreak sia che la ragione C fa questa conversione implicita è perché mallocnon può restituire un puntatore al tipo allocato. newè C ++ che restituisce un puntatore al tipo allocato, quindi il codice C ++ correttamente scritto non avrà mai alcun void *s da trasmettere.
Gort the Robot

3
A parte questo, non è un altro tipo di puntatore. È necessario applicare solo i puntatori di dati.
Deduplicatore

3
@ wolfPack88 hai sbagliato la tua storia. C ++ aveva void, C no . Quando quella parola / idea è stato aggiunto al C, hanno cambiato per soddisfare le esigenze di C. E 'stato poco dopo tipi di puntatore iniziato ad essere controllati a tutti . Vedi se riesci a trovare il libretto di descrizione C di K&R online o una copia vintage di un testo di programmazione C come C Primer di Waite Group . ANSI C era pieno, con funzionalità backportate o ispirate al C ++, e K&R C era molto più semplice. Quindi è più corretto che il C ++ abbia esteso il C così com'era in quel momento, e il C che sai è stato rimosso dal C ++.
JDługosz,

Risposte:


39

Poiché le conversioni di tipo implicito non sono in genere sicure e C ++ assume una posizione più sicura rispetto alla digitazione in C.

In genere C consentirà conversioni implicite, anche se la maggior parte delle probabilità è che la conversione sia un errore. Questo perché C presume che il programmatore sappia esattamente cosa stanno facendo e, in caso contrario, è il problema del programmatore, non il problema del compilatore.

Il C ++ di solito non consente le cose che potrebbero essere potenzialmente errori e richiede che tu dichiari esplicitamente la tua intenzione con un cast di tipo. Questo perché C ++ sta cercando di essere facile da programmare.

Potresti chiedere come mai è amichevole quando in realtà ti richiede di digitare di più.

Bene, vedi, ogni data riga di codice, in qualsiasi programma, in qualsiasi linguaggio di programmazione, sarà generalmente letta molte volte più di quanto non sarà scritta (*). Quindi, la facilità di lettura è molto più importante della facilità di scrittura. E durante la lettura, avere conversioni potenzialmente non sicure si distingue per cast di tipo esplicito aiuta a capire cosa sta succedendo e ad avere un certo livello di certezza che ciò che sta accadendo è in realtà ciò che doveva succedere.

Inoltre, l'inconveniente di dover digitare il cast esplicito è banale rispetto all'inconveniente di ore e ore di risoluzione dei problemi per trovare un bug che è stato causato da un incarico errato di cui avresti potuto essere avvisato, ma mai.

(*) Idealmente, verrà scritto una sola volta, ma verrà letto ogni volta che qualcuno deve esaminarlo per determinarne l'idoneità per il riutilizzo e ogni volta che è in corso la risoluzione dei problemi e ogni volta che qualcuno deve aggiungere codice vicino ad esso, e poi ogni volta che c'è la risoluzione dei problemi del codice vicino e così via. Questo è vero in tutti i casi tranne che per gli script "scrivi una volta, esegui, poi butta via", e quindi non sorprende che la maggior parte dei linguaggi di scripting abbia una sintassi che facilita la scrittura con totale disprezzo per facilità di lettura. Hai mai pensato che il perl sia completamente incomprensibile? Non sei solo. Pensa a tali lingue come lingue di "sola scrittura".


C è essenzialmente un passo sopra il codice macchina. Posso perdonare C per cose come questa.
Qix

8
I programmi (qualunque sia la lingua) devono essere letti. Le operazioni esplicite si distinguono.
Matthieu M.

10
Vale la pena notare che i cast void*sono più sicuri in C ++, perché con il modo in cui alcune funzionalità OOP sono implementate in C ++, il puntatore allo stesso oggetto potrebbe avere un valore diverso a seconda del tipo di puntatore.
hyde,

@MatthieuM. Verissimo. Grazie per averlo aggiunto, vale la pena far parte della risposta.
Mike Nakis,

1
@MatthieuM .: Ah, ma non vuoi davvero fare tutto in modo esplicito. La leggibilità non è migliorata avendo più da leggere. Sebbene a questo punto l'equilibrio sia chiaramente per essere esplicito.
Deduplicatore

28

Ecco cosa dice Stroustrup :

In C, puoi implicitamente convertire un vuoto * in un T *. Questo non è sicuro

Quindi continua a mostrare un esempio di come il vuoto * possa essere pericoloso e dice:

... Di conseguenza, in C ++, per ottenere un T * da un vuoto * è necessario un cast esplicito. ...

Infine, osserva:

Uno degli usi più comuni di questa conversione non sicura in C è quello di assegnare il risultato di malloc () a un puntatore adatto. Per esempio:

int * p = malloc (sizeof (int));

In C ++, utilizzare il nuovo operatore typesafe:

int * p = new int;

Ne approfondisce molti dettagli in The Design and Evolution of C ++ .

Quindi la risposta si riduce a: il progettista del linguaggio crede che sia un modello non sicuro, e quindi lo ha reso illegale e ha fornito modi alternativi per ottenere ciò per cui il modello era normalmente utilizzato.


1
In riferimento mallocall'esempio, vale la pena notare che malloc(ovviamente) non chiamerà un costruttore, quindi se si tratta di un tipo di classe per cui si sta allocando la memoria, essere in grado di eseguire il cast implicitamente sul tipo di classe sarebbe fuorviante.
chbaker0

Questa è un'ottima risposta, probabilmente migliore della mia. Non mi piace solo l'approccio "argomento dall'autorità".
Mike Nakis,

"new int" - wow, come novizio del C ++ avevo problemi a pensare a un modo per aggiungere un tipo base all'heap, e non sapevo nemmeno che tu potessi farlo. -1 uso per malloc.
Katana314

3
@MikeNakisFWIW, il mio intento era di integrare la tua risposta. In questo caso, la domanda era "perché l'hanno fatto", quindi ho pensato che le notizie dal capo designer fossero giustificate.
Gort il robot

11

In C, non è necessario trasmettere un vuoto * a nessun altro tipo di puntatore, viene sempre promosso in modo sicuro.

È sempre promosso, sì, ma a malapena sicuro .

C ++ disabilita questo comportamento proprio perché tenta di avere un sistema di tipo più sicuro di C e questo comportamento non è sicuro.


Considerare in generale questi 3 approcci per digitare la conversione:

  1. costringere l'utente a scrivere tutto, quindi tutte le conversioni sono esplicite
  2. supponiamo che l'utente sappia cosa stanno facendo e converta qualsiasi tipo in qualsiasi altro
  3. implementare un sistema di tipi completo con generici sicuri (tipi, nuove espressioni), operatori di conversione espliciti definiti dall'utente e forzare conversioni esplicite solo di cose che il compilatore non può vedere sono implicitamente sicure

Bene, 1 è brutto e un ostacolo pratico per fare qualsiasi cosa, ma potrebbe davvero essere usato dove è necessaria grande cura. C ha optato approssimativamente per 2, che è più facile da implementare, e C ++ per 3, che è più difficile da implementare ma più sicuro.


È stata una lunga strada difficile da raggiungere, con le eccezioni aggiunte in seguito e modelli ancora in seguito.
JDługosz,

Bene, i modelli non sono realmente necessari per un sistema di tipi così sicuro: le lingue basate su Hindley-Milner vanno d'accordo senza di loro abbastanza bene, senza conversioni implicite e senza la necessità di scrivere esplicitamente i tipi. (Naturalmente, queste lingue si basano sulla cancellazione dei tipi / sulla raccolta dei rifiuti / sul polimorfismo di tipo superiore, cose che il C ++ preferisce piuttosto evitare.)
leftaroundabout

1

Per definizione, un puntatore vuoto può puntare a qualsiasi cosa. Qualsiasi puntatore può essere convertito in un puntatore vuoto e, quindi, sarai in grado di riconvertirlo arrivando allo stesso identico valore. Tuttavia, i puntatori ad altri tipi possono avere vincoli, come le restrizioni di allineamento. Ad esempio, immagina un'architettura in cui i caratteri possono occupare qualsiasi indirizzo di memoria, ma i numeri interi devono iniziare su limiti di indirizzo pari. In alcune architetture, i puntatori a numeri interi potrebbero persino contare 16, 32 o 64 bit alla volta, in modo che un carattere * possa effettivamente avere un multiplo del valore numerico di int * mentre punta allo stesso posto in memoria. In questo caso, la conversione da un vuoto * in realtà arrotonderebbe i bit che non possono essere recuperati e quindi non sono reversibili.

In parole povere, il puntatore vuoto può puntare a qualsiasi cosa, comprese le cose che altri puntatori potrebbero non essere in grado di indicare. Pertanto, la conversione al puntatore vuoto è sicura ma non viceversa.

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.