Esiste un uso legittimo di void *?


87

Esiste un uso legittimo di void*in C ++? O è stato introdotto perché ce l'aveva C?

Solo per ricapitolare i miei pensieri:

Input : Se vogliamo consentire più tipi di input, possiamo sovraccaricare funzioni e metodi, in alternativa possiamo definire una classe base comune o un modello (grazie per averlo menzionato nelle risposte). In entrambi i casi il codice diventa più descrittivo e meno soggetto a errori (a condizione che la classe base sia implementata in modo sano).

Risultato : non riesco a pensare a nessuna situazione in cui preferirei ricevere void*invece di qualcosa derivato da una classe base nota.

Giusto per chiarire cosa intendo: non sto chiedendo specificamente se esiste un caso d'uso per void*, ma se c'è un caso in cui void*è la scelta migliore o l'unica disponibile. A cui hanno risposto perfettamente diverse persone di seguito.


3
che ne dici di quando vuoi avere più tipi come int e std :: string?
Amir

12
@Amir, variant, any, contrassegnata con l'unione. Tutto ciò che può dirti il ​​tipo effettivo di contenuto e più sicuro da usare.
Revolver_Ocelot

15
"C l'ha" è una giustificazione abbastanza forte, non c'è bisogno di cercare di più. Evitarlo il più possibile è una buona cosa in entrambe le lingue.
n. 'pronomi' m.

1
Solo una cosa: l'interoperabilità con le API in stile C è imbarazzante senza di essa.
Martin James

1
Un utilizzo interessante è la cancellazione del tipo per i vettori di puntatori
Paolo M

Risposte:


81

void*è almeno necessario come risultato di ::operator new(anche ogni operator new...) e di malloce come argomento newdell'operatore di posizionamento .

void*può essere pensato come il supertipo comune di ogni tipo di puntatore. Quindi non significa esattamente puntatore a void, ma puntatore a qualsiasi cosa.

A proposito, se si desidera conservare alcuni dati per diverse variabili globali non correlate, è possibile utilizzarne alcuni std::map<void*,int> score; quindi, dopo aver dichiarato global int x;and double y;and std::string s;do score[&x]=1;and score[&y]=2;andscore[&z]=3;

memset vuole un void*indirizzo (i più generici)

Inoltre, i sistemi POSIX hanno dlsym e il suo tipo di ritorno evidentemente dovrebbe esserevoid*


1
Questo è un ottimo punto. Ho dimenticato di dire che stavo pensando più da un utente del linguaggio piuttosto che dal lato dell'implementazione. Un'altra cosa che non capisco però: è nuova una funzione? L'ho pensato più come una parola chiave
magu_

9
new è un operatore, come + e sizeof.
Joshua

7
Ovviamente la memoria potrebbe essere restituita anche tramite a char *. Sono molto vicini su questo aspetto, anche se il significato è un po 'diverso.
edmz

@Giosuè. Non lo sapevo. Grazie per avermi insegnato questo. Poiché C++è scritto in C++questo è un motivo sufficiente per avere void*. Devo amare questo sito. Fai una domanda impara molto.
magu_

3
@black indietro ai vecchi tempi, C non ha nemmeno hanno un void*tipo. Tutte le funzioni di libreria standard utilizzatechar*
Cole Johnson

28

Ci sono diversi motivi per utilizzare void*, i 3 più comuni sono:

  1. interagire con una libreria C utilizzando void*nella sua interfaccia
  2. cancellazione del tipo
  3. che denota la memoria non tipizzata

In ordine inverso, indicare la memoria non tipizzata con void*(3) invece di char*(o varianti) aiuta a prevenire operazioni aritmetiche di puntatori accidentali; ci sono pochissime operazioni disponibili, void*quindi di solito richiede il casting prima di essere utile. E, naturalmente, proprio come conchar* con l'aliasing non ci sono problemi.

La cancellazione del tipo (2) è ancora utilizzata in C ++, in combinazione con i modelli o meno:

  • il codice non generico aiuta a ridurre il bloat binario, è utile nei percorsi freddi anche nel codice generico
  • Il codice non generico è necessario a volte per l'archiviazione, anche in contenitori generici come std::function

E ovviamente, quando l'interfaccia di cui ti occupi usa void*(1), hai poca scelta.


15

Oh si. Anche in C ++ a volte usiamo void *piuttosto che template<class T*>perché a volte il codice extra dall'espansione del modello pesa troppo.

Di solito lo userei come implementazione effettiva del tipo e il tipo di modello erediterà da esso e avvolgerà i cast.

Inoltre, devono essere utilizzati gli allocatori slab personalizzati (nuove implementazioni dell'operatore) void *. Questo è uno dei motivi per cui g ++ ha aggiunto un'estensione per consentire l'aritmatica del puntatore void *come se fosse di dimensione 1.


La ringrazio per la risposta. Hai ragione ho dimenticato di menzionare i modelli. Ma non sarebbero i modelli ancora meglio adatti per l'attività dal momento che il raramente introdurre una riduzione delle prestazioni (? Stackoverflow.com/questions/2442358/... )
magu_

1
I modelli introducono una riduzione delle dimensioni del codice, che nella maggior parte dei casi è anche una riduzione delle prestazioni.
Joshua

struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;è un simulatore di vuoto minimo - * - che utilizza modelli.
user253751

3
@ Joshua: Avrai bisogno di una citazione per "la maggior parte".
user541686

@Mehrdad: vedi cache L1.
Joshua

10

Input: se vogliamo consentire più tipi di input, possiamo sovraccaricare funzioni e metodi

Vero.

in alternativa possiamo definire una classe base comune.

Questo è parzialmente vero: e se non puoi definire una classe base comune, un'interfaccia o simili? Per definirli è necessario avere accesso al codice sorgente, cosa che spesso non è possibile.

Non hai menzionato i modelli. Tuttavia, i modelli non possono aiutarti con il polimorfismo: funzionano con tipi statici, cioè conosciuti in fase di compilazione.

void*può essere considerato come il minimo comune denominatore. In C ++, in genere non lo fai bisogno perché (i) non puoi intrinsecamente farci molto e (ii) ci sono quasi sempre soluzioni migliori.

Inoltre, in genere finirai per convertirlo in altri tipi di cemento. Ecco perché di char *solito è migliore, anche se potrebbe indicare che ti aspetti una stringa in stile C, piuttosto che un puro blocco di dati. Ecco perché void*è meglio di char*quello, perché consente il cast implicito da altri tipi di puntatore.

Dovresti ricevere alcuni dati, lavorarci e produrre un output; per ottenere ciò, devi conoscere i dati con cui stai lavorando, altrimenti hai un problema diverso che non è quello che stavi risolvendo originariamente. Molte lingue non ce l'hanno void*e non hanno problemi con questo, ad esempio.

Un altro uso legittimo

Quando si stampano indirizzi di puntatore con funzioni come printfil puntatore deve avere il void*tipo e, quindi, potrebbe essere necessario un cast a void*


7

Sì, è utile come qualsiasi altra cosa nella lingua.
Ad esempio, puoi usarlo per cancellare il tipo di una classe che sei in grado di lanciare staticamente al tipo giusto quando necessario, in modo da avere un'interfaccia minimale e flessibile.

In quella risposta c'è un esempio di utilizzo che dovrebbe darti un'idea.
Lo copio e incollo qui sotto per motivi di chiarezza:

class Dispatcher {
    Dispatcher() { }

    template<class C, void(C::*M)() = C::receive>
    static void invoke(void *instance) {
        (static_cast<C*>(instance)->*M)();
    }

public:
    template<class C, void(C::*M)() = &C::receive>
    static Dispatcher create(C *instance) {
        Dispatcher d;
        d.fn = &invoke<C, M>;
        d.instance = instance;
        return d;
    }

    void operator()() {
        (fn)(instance);
    }

private:
    using Fn = void(*)(void *);
    Fn fn;
    void *instance;
};

Ovviamente, questo è solo uno dei tanti usi di void*.


4

Interfacciamento con una funzione di libreria esterna che restituisce un puntatore. Eccone uno per un'applicazione Ada.

extern "C" { void* ada_function();}

void* m_status_ptr = ada_function();

Questo restituisce un puntatore a qualunque cosa Ada voleva dirti. Non devi farci niente di speciale, puoi restituirlo ad Ada per fare la prossima cosa. In effetti, districare un puntatore Ada in C ++ non è banale.


Non potremmo usare autoinvece in questo caso? Supponendo che il tipo sia noto in fase di compilazione.
magu_

Ah, al giorno d'oggi probabilmente potremmo. Quel codice è di alcuni anni fa, quando ho realizzato alcuni wrapper Ada.
RedSonja

I tipi Ada possono essere diabolici: fanno il loro, sai, e provano un piacere perverso nel renderlo complicato. Non mi è stato permesso di cambiare l'interfaccia, sarebbe stato troppo facile, e ha restituito alcune cose brutte nascoste in quei puntatori vuoti. Uffa.
RedSonja

2

In breve, C ++ come linguaggio rigoroso (non tenendo conto delle reliquie C come malloc () ) richiede void * poiché non ha un genitore comune di tutti i tipi possibili. A differenza di ObjC, ad esempio, che ha object .


malloced newentrambi restituiscono void *, quindi avresti bisogno se anche ci fosse una classe di oggetti in C ++
Dmitry Grigoryev

malloc è relitto, ma nelle lingue rigorose il nuovo dovrebbe restituire l'oggetto *
nredko

come si alloca quindi un array di numeri interi?
Dmitry Grigoryev

@DmitryGrigoryev operator new()ritorna void *, ma l' newespressione no
MM

1. Non sono sicuro che mi piacerebbe vedere un oggetto di classe di base virtuale sopra ogni classe e tipo , incluso int2. se è EBC non virtuale, in che modo è diverso da void*?
lorro

1

La prima cosa che mi viene in mente (che sospetto sia un caso concreto di un paio delle risposte precedenti) è la capacità di passare un'istanza di oggetto a un threadproc in Windows.

Ho un paio di classi C ++ che devono farlo, hanno implementazioni di thread di lavoro e il parametro LPVOID nell'API CreateThread () ottiene un indirizzo di un'implementazione del metodo statico nella classe in modo che il thread di lavoro possa fare il lavoro con un'istanza specifica della classe. Un semplice cast statico nel threadproc restituisce l'istanza con cui lavorare, consentendo a ogni oggetto istanziato di avere un thread di lavoro da una singola implementazione del metodo statico.


0

In caso di ereditarietà multipla, se è necessario ottenere un puntatore al primo byte di un blocco di memoria occupato da un oggetto, è possibile dynamic_castfarlo void*.

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.