web-dev-qa-db-fra.com

Constructeur par défaut en C

Existe-t-il un moyen d'avoir une sorte de constructeur par défaut (comme un C++) pour les types d'utilisateurs C définis avec une structure?

J'ai déjà une macro qui fonctionne comme un initialiseur rapide (comme celle pour pthread_mutex) mais je voulais savoir si vous pouvez par hasard avoir certains (ou tous) champs d'une structure remplis à la déclaration.

Par exemple, avec le pthread_mutex exemple, je voudrais

pthread_mutex_t my_mutex;

avoir le même effet que

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
26
claf

Vous pouvez créer des fonctions d'initialisation qui prennent un pointeur sur une structure. C'était une pratique courante.

Fonctionne également qui crée une structure et l'initialise (comme une usine) - il n'y a donc jamais un moment où la structure est "non initialisée" dans le code "client". Bien sûr - cela suppose que les gens suivent la convention et utilisent le "constructeur"/usine ...

horrible pseudo code sans vérification d'erreur sur malloc ou gratuit

somestruct* somestruct_factory(/* per haps some initializer agrs? */)
{
  malloc some stuff
  fill in some stuff
  return pointer to malloced stuff
}


void somestruct_destructor(somestruct*)
{
  do cleanup stuff and also free pointer
  free(somestruct);
}

Quelqu'un viendra probablement et expliquera comment certains préprocesseurs/compilateurs C++ ont travaillé pour faire tout cela en C.

37
Tim

C++ est différent de C dans ce cas par le fait qu'il n'a pas de "classes". Cependant, C (comme beaucoup d'autres langages) peut toujours être utilisé pour la programmation orientée objet. Dans ce cas, votre constructeur peut être une fonction qui initialise une structure. C'est la même chose que les constructeurs (seulement une syntaxe différente). Une autre différence est que vous devez allouer l'objet à l'aide de malloc () (ou d'une variante). En C++, vous utiliseriez simplement le "nouvel" opérateur.

par exemple. Code C++:

class A {
  public:
    A() { a = 0; }
    int a;
};

int main() 
{
  A b;
  A *c = new A;
  return 0;
}

code C équivalent:

struct A {
  int a;
};

void init_A_types(struct A* t)
{
   t->a = 0;
}

int main()
{
   struct A b;
   struct A *c = malloc(sizeof(struct A));
   init_A_types(&b);
   init_A_types(c);
   return 0;
}

la fonction 'init_A_types' fonctionne comme un constructeur le ferait en C++.

20
Reed

Parlons de la solution d'ingénierie complète qui était considérée comme la meilleure pratique dans les temps anciens.

Le problème avec les structures est que tout est public, donc il n'y a pas de données cachées.

Nous pouvons régler cela.

Vous créez deux fichiers d'en-tête. L'un est le fichier d'en-tête "public" utilisé par les clients de votre code. Il contient des définitions comme celle-ci:

typedef struct t_ProcessStruct *t_ProcessHandle;

extern t_ProcessHandle NewProcess();
extern void DisposeProcess(t_ProcessHandle handle);

typedef struct t_PermissionsStruct *t_PermissionsHandle;

extern t_PermissionsHandle NewPermissions();
extern void DisposePermissions(t_PermissionsHandle handle);

extern void SetProcessPermissions(t_ProcessHandle proc, t_PermissionsHandle perm);

puis vous créez un fichier d'en-tête privé qui contient des définitions comme celle-ci:

typedef void (*fDisposeFunction)(void *memoryBlock);

typedef struct {
    fDisposeFunction _dispose;
} t_DisposableStruct;

typedef struct {
    t_DisposableStruct_disposer; /* must be first */
    PID _pid;
    /* etc */
} t_ProcessStruct;

typedef struct {
    t_DisposableStruct_disposer; /* must be first */
    PERM_FLAGS _flags;
    /* etc */
} t_PermissionsStruct;

puis dans votre implémentation, vous pouvez faire quelque chose comme ceci:

static void DisposeMallocBlock(void *process) { if (process) free(process); }

static void *NewMallocedDisposer(size_t size)
{
    assert(size > sizeof(t_DisposableStruct);
    t_DisposableStruct *disp = (t_DisposableStruct *)malloc(size);
    if (disp) {
       disp->_dispose = DisposeMallocBlock;
    }
    return disp;
}

static void DisposeUsingDisposer(t_DisposableStruct *ds)
{
    assert(ds);
    ds->_dispose(ds);
}

t_ProcessHandle NewProcess()
{
    t_ProcessHandle proc =  (t_ProcessHandle)NewMallocedDisposer(sizeof(t_ProcessStruct));
    if (proc) {
        proc->PID = NextPID(); /* etc */
    }
    return proc;
}

void DisposeProcess(t_ProcessHandle proc)
{
    DisposeUsingDisposer(&(proc->_disposer));
}

Ce qui se passe, c'est que vous faites des déclarations avancées pour vos structures dans vos fichiers d'en-tête publics. Maintenant, vos structures sont opaques, ce qui signifie que les clients ne peuvent pas s'en servir. Ensuite, dans la déclaration complète, vous incluez un destructeur au début de chaque structure que vous pouvez appeler de manière générique. Vous pouvez utiliser le même allocateur malloc pour tout le monde, la même fonction d'élimination et ainsi. Vous rendez publiques les fonctions set/get des éléments que vous souhaitez exposer.

Du coup, votre code est beaucoup plus sain d'esprit. Vous ne pouvez obtenir des structures qu'à partir d'allocateurs ou de fonctions qui appellent des allocateurs, ce qui signifie que vous pouvez initialiser le goulot d'étranglement. Vous construisez des destructeurs afin que l'objet puisse être détruit. Et allez-y. Soit dit en passant, un meilleur nom que t_DisposableStruct pourrait être t_vTableStruct, car c'est ce que c'est. Vous pouvez maintenant créer un héritage virtuel en ayant un vTableStruct qui est tous des pointeurs de fonction. Vous pouvez également faire des choses que vous ne pouvez pas faire dans un langage oo pur (généralement), comme changer des éléments sélectionnés de la table virtuelle à la volée.

Le point important est qu'il existe is un modèle d'ingénierie pour rendre les structures sûres et initialisables.

11
plinth

Non, pas directement. Le mieux que vous puissiez faire est d'écrire une fonction qui a alloué une instance et rempli certains des champs.

8

Vous pouvez écrire une fonction qui renvoie la structure C:

struct file create_file(int i, float f) {
    struct file obj = { i, f };
    // other code here...
    return obj;
}

Si vous vous demandez si vous pouvez avoir des fonctions membres "normales" dans C. Eh bien, vous pouvez dans une certaine mesure. Je préfère le style objet comme premier argument. Vous passez un pointeur sur votre structure comme premier argument. De cette façon, vous pouvez avoir plusieurs fonctions, définissant l'interface de vos objets:

int file_get_integer(struct file *self) { return self->i; }
float file_get_float(struct file *self) { return self->f; }

Si vous écrivez dans ce style, ce que vous avez à la fin est un type de données abstrait. J'ai vu des gars émuler la syntaxe d'appel de fonction membre utilisée en C++ en ayant des pointeurs de fonction dans leur structure, puis en faisant:

obj.get_integer(&obj);

Il est utilisé par le noyau Linux pour définir l'interface avec les pilotes du système de fichiers. C'est un style d'écriture que l'on peut aimer ou ne pas aimer. Je n'aime pas trop ça, car je continue à utiliser des membres de structures pour les données et non pour émuler des appels de fonctions membres pour ressembler à des langages orientés objet populaires.

4

Fondamentalement, C++ crée des listes de pointeurs qui contiennent les adresses des méthodes. Cette liste est appelée une définition de classe (il y a plus de données dans la classe def, mais nous l'ignorons pour l'instant).

Un modèle courant pour avoir des "classes" en C pur est de définir une "classe struct". L'un des champs de la structure est une fonction de fabrique qui renvoie des "instances" de la classe. Je suggère d'utiliser une macro pour masquer les dominantes:

typedef struct __class * class;
typedef void (*ctor_ptr)(class);
struct class {
    char * name;
    ctor_ptr ctor;
    ... destructor and other stuff ...
}

#define NEW(clz) ((struct something *)(((struct class *)clz)->ctor(clz)))

Maintenant, vous pouvez définir les classes que vous avez en créant des structures de type "struct class" pour chaque classe que vous avez, puis appelez le constructeur qui y est stocké. Il en va de même pour le destructeur, etc.

Si vous voulez des méthodes pour vos instances, vous devez les mettre dans les structures de classe et garder un pointeur sur la classe dans la structure d'instance:

#define NEW_SOMETHING() ((struct something *)NEW(&something_definition))
#define METHOD(inst, arg) ((struct something_class *)(((struct something *)inst)->clz)->method(inst, arg))

NEW_SOMETHING créera une nouvelle instance de "quelque chose" en utilisant la définition de classe stockée dans la structure something_definition. METHOD invoquera une "méthode" sur cette instance. Notez que pour le vrai code, vous voudrez vérifier que inst est en fait une instance de something (comparer les pointeurs de classe, quelque chose comme ça).

Avoir un héritage est un peu délicat et laissé comme exercice pour le lecteur.

Notez que vous pouvez faire tout ce que vous pouvez faire en C++ en pur C. Un compilateur C++ ne remplace pas comme par magie votre CPU par autre chose. Il faut juste beaucoup plus de code pour le faire.

Si vous voulez regarder un exemple, consultez glib (partie du projet Gtk +).

3
Aaron Digulla

Voici un peu de magie macro autour des littéraux composés malloc(), memcpy() et C99:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define new(TYPE, ...) memdup(&(TYPE){ __VA_ARGS__ }, sizeof(TYPE))

void * memdup(const void * obj, size_t size)
{
    void * copy = malloc(size);
    return copy ? memcpy(copy, obj, size) : NULL;
}

struct point
{
    int x;
    int y;
};

int main()
{
    int * i = new(int, 1);
    struct point * p = new(struct point, 2, 3);

    printf("%i %i %i", *i, p->x, p->y);

    return 0;
}
2
Christoph

L'utilisation de fonctions pour créer et supprimer des structures a déjà été mentionnée. Mais dans un commentaire, vous avez mentionné que vous vouliez un "constructeur par défaut" - je pense que vous voulez dire que vous voulez initialiser certains (tous?) Des champs struct aux valeurs par défaut.

Cela se fait en C en utilisant une convention de codage - soit des fonctions, des macros ou un mix. Je fais habituellement quelque chose comme ceci -

struct some_struct {
    int a;
    float b;
};
#define some_struct_DEFAULT { 0, 0.0f}
struct some_struct *some_struct_create(void) {
    struct some_struct *ptr = malloc(sizeof some_struct);
    if(!ptr)
        return ptr;

    *ptr = some_struct_DEFAULT;
    return ptr;
}
// (...)
struct some_struct on_stack = some_struct_DEFAULT;
struct some_struct *on_heap = some_struct_create();
2
gnud

La jachère est le programme simple qui utilise un constructeur. La fonction "default_constructor" est appelée à l'intérieur de main sans aucun appel de fonction explicite.

#include        <stdio.h> 

void __attribute__ ((constructor)) default_constructor() 
{ 
    printf("%s\n", __FUNCTION__); 
} 

int main() 
{ 
    printf("%s\n",__FUNCTION__);
    return 0; 
}

Sortie: default_constructor main

Dans la fonction constructeur, vous pouvez inclure des instructions d'initialisation dans la fonction constructeur et enfin faire la libération dans le destructeur comme jachère,

#include    <stdio.h> 
#include    <stdlib.h>

struct somestruct
{
    int     empid;
    char *  name;
};

struct somestruct * str1;

void __attribute__ ((constructor)) a_constructor() 
{ 
    str1 = (struct somestruct *) malloc (sizeof(struct somestruct));
    str1 -> empid = 30228;
    str1 -> name = "Nandan";
} 

void __attribute__ ((destructor)) a_destructor()
{
    free(str1);
}

int main() 
{ 
    printf("ID = %d\nName = %s\n", str1 -> empid, str1 -> name);
    return 0;
}

J'espère que ceci vous aidera.

2
Nandan Bharadwaj

En supposant que vous vouliez le faire en C, votre question ne concerne pas les structures en C++:

Ce que je fais habituellement, c'est que je crée simplement une fonction, init_wwhat, qui prend un pointeur sur la structure (ou une autre variable), et lui donne les valeurs que je veux.

S'il s'agit d'une variable globale (qui est initialisée à zéro) vous pourriez sorte de simuler un constructeur sans argument en ayant un indicateur "initialisé" dedans, puis laissez vos autres fonctions vérifier cet indicateur, et initialiser la structure si le drapeau est nul. Je ne suis pas du tout sûr que ce soit une bonne idée.

Et, comme quelqu'un l'a noté, vous pouvez également faire quelque chose d'horrible avec des macros ...

2

Vous voudrez peut-être jeter un œil à un compilateur C++. Il vous offre plus de fonctionnalités orientées objet que vous ne vous en souvenez dans un langage ayant une syntaxe de type C d'une manière plus élégante et standard que d'essayer de boulonner des constructeurs et ainsi de suite sur C avec des bibliothèques et des macros.

Si cela ne fonctionne pas pour vous, jetez un œil à GObject. http://en.wikipedia.org/wiki/GObject . Il s'agit d'un système d'objets pour C utilisé dans GTK et Gnome qui lance à peu près les "objets en C" à 11.

De Wikipédia:

Le GLib Object System, ou GObject, est une bibliothèque de logiciels gratuits (couverte par la LGPL) qui fournit un système d'objets portable et une interopérabilité trans-langage transparente.

Le système prend en charge les constructeurs, les destructeurs, l'héritage unique, les interfaces, les méthodes publiques et privées virtuelles, etc. C'est aussi beaucoup plus fastidieux et difficile que de faire les mêmes choses en C++. S'amuser!

1
joeforker

Pas vraiment. Si je me souviens bien, les structures les plus proches sont les structures. Vous allouez de la mémoire et remplissez les champs. Je ne vois pas comment vous pourriez créer une chose de type constructeur générique pour cela. Théoriquement, vous pourriez écrire une macro qui ferait une partie de cela. Je ne sais pas si cela en vaut vraiment la peine.

1
paul

Non, les structures ne sont rien d'autre qu'un tas de données. Vous ne pouvez pas déclarer de fonctions dans les structures, il n'y a donc aucun moyen de créer un constructeur pour cela.

0
soulmerge