2010-11-29 6 views
5

j'ai vu cet exemple sur un site Web, et les sites Internet mentionne: - « Une de ses utilisations (pointeurs vides) peut être de passer des paramètres génériques à une fonction »pointeurs vides en C++

// increaser 
#include <iostream> 
using namespace std; 

void increase (void* data, int psize) 
{ 
    if (psize == sizeof(char)) 
    { char* pchar; pchar=(char*)data; ++(*pchar); } 
    else if (psize == sizeof(int)) 
    { int* pint; pint=(int*)data; ++(*pint); } 
} 

int main() 
{ 
    char a = 'x'; 
    int b = 1602; 
    increase (&a,sizeof(a)); 
    increase (&b,sizeof(b)); 
    cout << a << ", " << b << endl; 
    return 0; 
} 

wouldn » t-il être plus simple d'écrire du code comme celui-ci?

void increaseChar (char* charData) 
{ 
    ++(*charData); 
} 

void increaseInt (int* intData) 
{ 
    ++(*intData); 
} 

int main() 
{ 
    char a = 'x'; 
    int b = 1602; 
    increaseChar (&a); 
    increaseInt (&b); 
    cout << a << ", " << b << endl; 
    string str; 
    cin >> str; 
    return 0; 
} 

C'est moins de code, et c'est vraiment simple. Et dans le premier code, j'ai dû envoyer la taille du type de données ici, je ne le fais pas!

+0

Mais maintenant vous avez deux fonctions au lieu d'une seule, ce qui signifie que vous ne pouvez pas (par exemple) enregistrer 'increase' en tant qu'auditeur d'événement dans un cadre. Vous avez raison de dire que l'exemple donné n'est pas un usage réaliste, et même dans un contexte plus approprié, il est inhabituel de passer un pointeur de données * et * une taille à un rappel. L'interface de 'pthread_create' est un exemple plus plausible, bien qu'en C++, vous préfériez utiliser une API C++-ish telle que Boost.Thread. –

+1

Le premier est un idiome C commun que * certaines * personnes pensent est également approprié en C++. Ce n'est généralement pas le cas. Utilisez l'héritage, les modèles, etc., autant que possible pour préserver la sécurité du type. –

+0

@larsman Je suis complètement d'accord. Cet exemple est seulement bon pour C. Pas pour C++. J'avais principalement utilisé '(void *)' lors de l'interfaçage avec le code C, comme un pointeur opaque. Et aussi avec MS COM en C++. AFAIR :: QueryInterface() utilise aussi un 'void * &' comme paramètre de sortie. –

Répondre

0

Votre code est définitivement préférable. void* Ne perd pas de semblant de taper la sécurité, vos surcharges typesafe sont beaucoup mieux. De bons instincts.

Pour prendre en charge plus de types génériques en C++, vous devez définir un modèle de classe ou de fonction, sans utiliser void*. Les références seraient préférables à des pointeurs, mais votre code pourrait être changé à ceci:

template <class T> T increase(T* value) 
{ 
    return ++(*value); 
} 

int main(int argc, char* argv[]) 
{ 
    int i(0); 
    increase<int>(&i); 

    char c(0); 
    increase<char>(&c); 

    return 0; 
} 

Quel est le site, comme une question d'intérêt?

+0

Eh bien, lol, c'est le site Web de cplusplus: http://www.cplusplus.com/doc/tutorial/pointers/ – w4j3d

+0

@ w4j3d - merci pour la note, ce n'est pas un bon conseil à http: //www.cplusplus. com/doc/tutorial/pointers/ –

0

Votre solution ne fonctionne que pour int ou char. Considérez la situation que vous voulez traverser un tableau de type personnalisé (struct, par exemple). Dans votre code, vous devrez ajouter une méthode pour le faire, mais dans l'exemple ci-dessus aucune ligne de could ne devrait être ajoutée pour obtenir l'exemple de travail.

13

Il est préférable de rendre le type de fonction sûr et générique. Il serait également préférable de prendre l'argument par référence plutôt que par pointeur:

template <typename T> 
void increment(T& data) { 
    ++data; 
} 

void* doit être évitée autant que possible en C++, parce que les modèles et l'héritage offrent des alternatives typées.

+2

Les modèles sont la voie à suivre. La sécurité de type est appliquée à la compilation. J'irais juste un peu plus loin et passerais les données comme 'T &'. Dans la plupart des cas, vous n'aurez même pas à écrire 'increment (i)', car le compilateur déduira généralement le type 'T' automatiquement. – Mephane

+0

+1. Et peut-être en utilisant des références au lieu de pointeurs ... – paercebal

+0

@Mephane, l'inférence de type devrait fonctionner ici aussi, je suppose? L'utilisation d'un pointeur n'est pas * si * mauvaise, comme vous le voyez explicitement à partir de l'appel de fonction, votre paramètre va être modifié. – Kos

0

« Une de ses utilisations (pointeurs vides) peut être de passer des paramètres génériques à une fonction »

Pas bon usage de style, pour être certain. Le passage de paramètres génériques aux fonctions se fait par:

a) modèles (et spécialisations éventuellement modèle) qui permet d'écrire du code en toute sécurité,
b) approches POO. J'aime votre meilleure solution increaseInt et increaseChar mieux. Vous pouvez réécrire comme:

template<typename T> 
void increase(T* pData) 
{ 
    ++(*pData); 
} 

Il fonctionnera pour tout type qui prend en charge ++ et être en sécurité.


Parfois, vous aviez encore besoin d'un pointeur vide comme un « paramètre générique » - par exemple lors de l'utilisation de la bibliothèque pthread populaire. Quand vous lancez un thread, vous pouvez lui envoyer des données arbitraires (de n'importe quel type que vous voulez), et c'est fait en utilisant - bien - un pointeur void*.La bibliothèque pthread est en C non C++, ce qui peut être l'une des raisons, mais vous pourriez vouloir l'interfacer avec C++ et cela nécessitera en effet d'utiliser le pointeur void* comme "type générique". Pas beaucoup de mal fait là, cependant; Rappelez-vous simplement que pour le codage quotidien, il existe des solutions plus élégantes.

0

En fait, je pense que l'exemple est assez mauvais. Un des exemples canoniques serait qsort de la bibliothèque C, où vous passez en void*ainsi que une fonction qui interprète les données.

Cependant, en C++, il existe de meilleurs mécanismes disponibles qui sont moins susceptibles de mal fonctionner silencieusement.

Par conséquent, l'utilisation de void* pour transmettre des données génériques autour devrait probablement se limiter à

cas
  1. où le ballonnement de code est important et
  2. Code
  3. qui interagit avec une autre langue, en particulier C.
0

Lorsque vous programmez C (pas C++) void *, vous pouvez seulement faire quelque chose de générique. Néanmoins, l'exemple est terrible. Ce schéma est souvent utilisé pour la mécanique de callback, où vous passez un pointeur de fonction et un void *. Par exemple:

void handle_crash(void* data) 
{ 
    Log* log = (Log*)data; 
    log->print("We crashed."); 
} 

Log log; 
set_crash_handler(&handle_crash, &log); 

Vous verrez cela souvent sur des frameworks C tels que libxml2 ou spidermonkey. Dans le cas de C, c'est la seule chose à faire pour l'accomplir. Ce n'est pas une solution très robuste. Si vous travaillez avec C++, vous avez plus d'options. La programmation générique de base peut être faite avec des modèles ou une surcharge comme mentionné dans d'autres réponses. Dans le cas où vous avez besoin d'un mécanisme de callback solide, vous pouvez regarder dans libsigC++ ou d'autres frameworks de "signaux et slots".