Je comprends l'utilisation du pointeur vide pour la mise en œuvre de malloc.
void* malloc ( size_t size );
Quelqu'un peut-il suggérer d'autres raisons ou fournir des scénarios où cela est utile dans la pratique.
Merci
Un bon scénario void*
l'utilisation est quand vous voulez implémenter des ADT génériques, au cas où vous ne savez pas quel type de données il va conserver et gérer. Par exemple, une liste chaînée comme celle-ci:
typedef struct node_t node;
struct
{
void* data;
node* prev, next;
} node_t;
typedef struct list_t list;
typedef void* (func)(void*) cpy_func;
typedef void (func)(void*) del_func;
struct
{
node* head, tail, curr;
cpy_func copy;
del_func delete;
} list_t;
initializeLinkedList(cpy_func cpy, del_func del);
//here you keep going defining an API
Ici, par exemple, vous passerez des pointeurs de fonction d'initialisation à d'autres fonctions qui seront capables de copier votre type de données dans votre liste et de le libérer ensuite. Donc, en utilisant void*
vous rendez votre liste plus générique.
Je pense void*
est resté en C++ uniquement à cause de la compatibilité descendante, car en C++ vous avez des moyens plus sûrs et sophistiqués pour obtenir le même résultat comme des modèles, des foncteurs, etc., et vous n'avez pas besoin d'utiliser malloc lors de la programmation de C++.
Concernant C++, je n'ai pas d'exemples utiles spécifiques.
Si vous vous connectez avec du code C et que vous devez passer par un objet C++, mais qu'une bibliothèque C ne prendra qu'un pointeur générique, alors lorsque vous récupérez le pointeur, vous devez le redistribuer au type approprié.
Les pointeurs vides ne devraient probablement pas être utilisés très souvent, mais ils peuvent vous aider lorsque vous essayez d'utiliser une fonction de bibliothèque qui fonctionne avec des pointeurs arbitraires et ne se soucie pas vraiment des données représentées par cette mémoire.
En C++, j'ai trouvé que le cas d'utilisation le plus convaincant pour les pointeurs void * est de donner au code la possibilité de stocker des "données utilisateur" arbitraires sur un objet qu'ils utilisent déjà.
Supposons que vous ayez écrit une classe représentant un Car
, pour une utilisation dans un logiciel qui fait des choses utiles avec des objets Car
(simulation du trafic, inventaire de voitures de location, etc.). Supposons maintenant que vous vous trouviez dans une situation où votre application souhaite garder une trace du contenu arbitraire du tronc d'un Car
. Les détails de ce qui est stocké dans le coffre ne sont pas importants pour la classe Car
, et peuvent être n'importe quoi - cela dépend vraiment du but de l'application utilisant la classe Car. Entrez le pointeur void *.
class Car
{
public:
// Existing methods of your Car class
void setContentsOfTrunk(void* contentsOfTrunk);
void* contentsOfTrunk() const;
private:
void* m_contentsOfTrunk;
}
Maintenant, toute application utilisant votre classe Car
a la possibilité d'attacher un objet de données arbitraire à un objet Car
existant de telle sorte qu'il puisse être obtenu à partir de tout code qui a le Car
objet. Le contenu du tronc "voyage avec" l'objet Car
, où qu'il aille dans votre code.
Il existe deux alternatives à l'utilisation de void * dans ce cas.
La première consiste à modéliser votre classe en fonction du type de l'objet de contenu de tronc:
template <class TrunkContentsType>
class Car
{
public:
// Existing methods of your Car class
void setContentsOfTrunk(TrunkContentsType contentsOfTrunk);
TrunkContentsType contentsOfTrunk() const;
private:
TrunkContentsType m_contentsOfTrunk;
}
Cela semble inutilement invasif. Le type de contenu du coffre n'est important que pour l'application. Les algorithmes et les structures de données travaillant avec des objets Car ne se soucient pas de ce qui se trouve dans le coffre. En modelant la classe, vous forcez les applications utilisant la classe à choisir un type pour le contenu du tronc, mais dans de nombreux cas, les applications ne se soucient pas non plus du contenu du tronc.
La deuxième alternative consiste à dériver une nouvelle classe de Car qui ajoute un membre de données et des accesseurs pour le contenu du tronc:
class Car
{
public:
// Existing methods of your Car class
// No methods having anything to do with trunk contents.
private:
// No data member representing trunk contents.
}
class CarWithTrunkContents
{
public:
// Existing methods of your Car class
void setContentsOfTrunk(TrunkContentsType contentsOfTrunk);
TrunkContentsType contentsOfTrunk() const;
private:
TrunkContentsType m_contentsOfTrunk;
}
La nouvelle classe CarWithTrunkContents
est une classe spécifique à l'application qui ajoute un membre de données du type dont l'application a besoin pour stocker le contenu du coffre sur la voiture. Cela semble également inutilement lourd. Pourquoi devez-vous dériver une toute nouvelle classe pour ajouter une donnée supplémentaire qui n'affecte pas le comportement de la classe? Et s'il est assez courant que les applications utilisant la classe Car
souhaitent stocker le contenu du tronc, pourquoi forcer chaque application à dériver une nouvelle classe pour leur type particulier de contenu du tronc?
Enfin, alors que mon exemple artificiel de contenu de tronc peut donner une image vivante de contenu de tronc arbitraire voyageant avec l'objet Car
, en pratique, vous fournirez probablement un mécanisme encore plus général pour attacher des données spécifiques à l'application au Car
:
class Car
{
public:
// Existing methods of your Car class
void setUserData(void* userData);
void* userData() const;
private:
void* m_userData;
}
De cette façon, une application peut attacher un objet représentant le contenu du coffre, ou un objet représentant le permis de conduire et l'immatriculation, ou un objet représentant le contrat de location, ou autre chose. J'ai vu ce type de pointeur void * appelé "userData" (c'est-à-dire compris par l'utilisateur de la classe), "blindData" (c'est-à-dire que la classe est aveugle au contenu de l'objet qu'elle transporte) ou "applicationData" ( c'est-à-dire des données de type et de finalité définies par l'application).
void
les pointeurs doivent être utilisés chaque fois que le contenu d'un bloc de données n'est pas important. Par exemple, lors de la copie de données, le contenu d'une zone mémoire est copié mais le format des données n'est pas important.
Pour les fonctions qui fonctionnent sur des blocs de mémoire sans avoir besoin de comprendre le contenu à l'aide de pointeurs void
clarifie la conception pour les utilisateurs afin qu'ils sachent que la fonction ne se soucie d'aucun format de données. Fonctionne souvent un codé pour prendre un char *
pour gérer les blocs de mémoire lorsque la fonction est en fait indépendante du contenu.
Une excellente façon d'en savoir plus sur void * et d'autres sujets en C est de regarder la première moitié des fantastiques "Paradigmes de programmation" de Stanford sur iTunes-U. Cela explique vraiment le vide * (génériques C) et les pointeurs en général de manière fantastique! Cela m'a vraiment aidé à mieux apprendre le C ...
L'une des utilisations les plus importantes consiste à utiliser void * si vous souhaitez pouvoir accepter différents types de données dans une fonction. (voici un exemple: http://142.132.30.225/programming/node87.html )
Voici un autre exemple de l'utilisation que vous pouvez en faire:
int i;
char c;
void *the_data;
i = 6;
c = 'a';
the_data = &i;
printf("the_data points to the integer value %d\n", *(int*) the_data);
the_data = &c;
printf("the_data now points to the character %c\n", *(char*) the_data);
Si vous ne voulez pas regarder les cours gratuits de stanford, je recommanderais de googler le pointeur vide et de lire tout le matériel là-bas.
Un autre exemple de ces "génériques" C, implémentés avec void *, est une fonction qsort standard:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
Vous pouvez trier un tableau de tout type: int, long, double, char * ou certains pointeurs struct ...
Il est couramment utilisé dans le code numérique, par exemple une fonction de solveur racine C pourrait ressembler à ça:
double find_root(double x0, double (*f)(double, void*), void* params)
{
/* stuff */
y = f(x, params);
/* other stuff */
}
params
est converti par f
dans une structure qu'il connaît, mais find_root
non.
void *
est vraiment un C-isme et permet à C de faire certaines choses qu'il ne pourrait raisonnablement pas faire autrement.
char *
ne peut être utilisé de manière portable pour rien, car différentes plates-formes peuvent créer différents types de pointeurs - un char *
n'est pas nécessairement traité de la même manière (ou a même la même taille) qu'un void *
.
Ainsi, lorsque le type de données n'est pas connu en C (ou est polymorphe ou autrement dynamique), alors void *
vous permet de générer le type de pointeur sous-jacent correct - qui peut pointer vers quoi que ce soit correctement.
En C++ void *
ne devrait généralement pas apparaître sauf dans le contexte de l'interfaçage avec le code C hérité sous une forme ou une autre.
Les pointeurs vides sont utiles lorsque vous écrivez du code qui doit s'exécuter sur plusieurs systèmes d'exploitation et doit être assez indépendant des API de framework sous-jacentes.
Par exemple, OS X, Windows et Linux ont tous le concept de base d'un objet fenêtre, mais ils sont tous très différents. J'ai donc du code commun qui les transmet sous forme de void *, puis des implémentations spécifiques à la plate-forme qui transforment le void * en type natif (HWND, etc.).
Mais, oui, comme d'autres l'ont dit dans ce fil, ce genre de chose est certainement à éviter, sauf si cela est nécessaire.
Il y a un grand avantage à utiliser le pointeur vide. La variable pointeur est une variable qui stocke l'adresse d'une autre variable. exemple:
int a;
int *x= &a;
Maintenant 'x' stocke l'adresse de la variable entière.
Mais celui-ci échoue:
float f;
int *x = &f;
Parce que la variable de pointeur entier ne peut stocker que l'adresse de variable entière. il en va de même pour les autres types de données.
Lorsque vous utilisez le pointeur void *, il donne un Edge pour stocker l'adresse de toute variable TYPE.
void *pointer = &i;
void *pointer = &f;
lors de la récupération, il doit être différé.
*((int*)pointer)
Alors, utilisez soigneusement le pointeur vide.
Cela pourrait vous aider, merci.
À côté de l'interfaçage avec C, je me retrouve à n'utiliser des pointeurs void que lorsque j'ai besoin de déboguer/tracer du code et que j'aime connaître l'adresse d'un certain pointeur.
SomeClass * myInstance;
// ...
std::clog << std::hex << static_cast< void* >(myInstance) << std::endl;
Imprime quelque chose comme
0x42A8C410
Et, à mon avis, documente bien ce que j'essaie de faire (connaître l'adresse du pointeur, rien sur l'instance)
Regardez sqlite3_exec () . Vous démarrez une requête SQL et souhaitez traiter les résultats d'une certaine manière (les stocker dans un conteneur). Vous appelez sqlite3_exec () et passez un pointeur de rappel et un pointeur void * à l'objet que vous voulez (conteneur inclus). Lorsque sqlite3_exec () s'exécute, il appelle le rappel pour chaque ligne récupérée et lui transmet ce pointeur void * afin que le rappel puisse transtyper le pointeur et faire ce que vous vouliez.
L'important est que sqlite3_exec () ne se soucie pas de ce que fait le rappel et du pointeur que vous passez. void * est exactement pour de tels pointeurs.