La société dans laquelle je travaille initialise toutes ses structures de données à l’aide d’une fonction d’initialisation comme celle-ci:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
InitializeFoo(Foo* const foo){
foo->a = x; //derived here based on other data
foo->b = y; //derived here based on other data
foo->c = z; //derived here based on other data
}
//initializing the structure
Foo foo;
InitializeFoo(&foo);
J'ai eu du mal à essayer d'initialiser mes structures comme ceci:
//the structure
typedef struct{
int a,b,c;
} Foo;
//the initialize function
Foo ConstructFoo(int a, int b, int c){
Foo foo;
foo.a = a; //part of parameter input (inputs derived outside of function)
foo.b = b; //part of parameter input (inputs derived outside of function)
foo.c = c; //part of parameter input (inputs derived outside of function)
return foo;
}
//initialize (or construct) the structure
Foo foo = ConstructFoo(x,y,z);
Y at-il un avantage à l’un sur l’autre?
Lequel devrais-je faire et comment pourrais-je le justifier en tant que meilleure pratique?
c
coding-style
constructors
initialization
Trevor Hickey
la source
la source
InitializeFoo()
est un constructeur. La seule différence par rapport à un constructeur C ++ est que lethis
pointeur est passé explicitement plutôt qu'implicitement. Le code compilé deInitializeFoo()
et un C ++ correspondantFoo::Foo()
sont exactement les mêmes.Réponses:
Dans la deuxième approche, vous n'aurez jamais un Foo semi-initialisé. Mettre toute la construction au même endroit semble un endroit plus sensé et plus évident.
Mais ... la 1ère voie n'est pas si mauvaise et est souvent utilisée dans de nombreux domaines (il y a même une discussion sur la meilleure façon d'injecter une dépendance, soit une injection de propriété comme votre 1ère voie, soit une injection de constructeur comme la 2e) . Ni est faux.
Donc, si aucun des deux n’est faux et que le reste de la société utilise l’approche n ° 1, vous devriez alors vous adapter au code existant et ne pas essayer de le gâcher en introduisant un nouveau modèle. C’est vraiment le facteur le plus important en jeu ici, jouez bien avec vos nouveaux amis et n’essayez pas d’être ce flocon de neige spécial qui fait les choses différemment.
la source
void SetupFoo(Foo *out, int a, int b, int c)
Foo
structure "semi-initialisée" ? La première approche effectue également toutes les initialisations en un seul endroit. (Ou envisagez-vous qu'une structure non initialiséeFoo
soit "semi-initialisée"?)Les deux approches regroupent le code d'initialisation en un seul appel de fonction. Jusqu'ici tout va bien.
Cependant, la deuxième approche pose deux problèmes:
Le second ne construit pas réellement l'objet résultant, il initialise un autre objet sur la pile, qui est ensuite copié dans l'objet final. C'est pourquoi je verrais la deuxième approche légèrement inférieure. Le refoulement que vous avez reçu est probablement dû à cette copie superflue.
Cela est encore pire lorsque vous dérivez une classe
Derived
deFoo
(struct sont largement utilisés pour l' orientation de l' objet en C): Avec la seconde approche, la fonctionConstructDerived()
appelleraitConstructFoo()
, copiez le temporaire résultantFoo
objet sur dans la fente de superclasse d'unDerived
objet; terminer l'initialisation de l'Derived
objet; seulement pour que l'objet résultant soit recopié à son retour. Ajoutez une troisième couche et le tout devient complètement ridicule.Avec la seconde approche, les
ConstructClass()
fonctions n’ont pas accès à l’adresse de l’objet en construction. Cela rend impossible la liaison d'objets pendant la construction, car cela est nécessaire lorsqu'un objet doit s'inscrire lui-même avec un autre objet pour un rappel.Enfin, tous ne
structs
sont pas des classes à part entière. Certainsstructs
regroupent efficacement un ensemble de variables, sans aucune restriction interne aux valeurs de ces variables.typedef struct Point { int x, y; } Point;
serait un bon exemple de cela. Pour ceux-ci, l'utilisation d'une fonction d'initialisation semble excessive. Dans ces cas, la syntaxe littérale composée peut être pratique (c'est C99):ou
la source
int x = 3;
ne pas stocker la chaînex
dans le binaire. L'adresse et les points d'héritage sont bons; l'échec présumé de l'élision est stupide.unsigned char
qui permettrait des optimisations pour lesquelles Une règle de crénelage stricte serait insuffisante, tout en clarifiant les attentes du programmeur.En fonction du contenu de la structure et du compilateur utilisé, l'une ou l'autre approche pourrait être plus rapide. Un schéma typique est que les structures répondant à certains critères peuvent être restituées dans des registres; pour les fonctions renvoyant d'autres types de structure, l'appelant doit allouer de l'espace pour la structure temporaire (généralement sur la pile) et transmettre son adresse en tant que paramètre "masqué"; dans les cas où le retour d'une fonction est stocké directement dans une variable locale dont l'adresse n'est pas détenue par un code extérieur, certains compilateurs peuvent être en mesure de transmettre directement l'adresse de cette variable.
Si un type de structure satisfait aux exigences d'une mise en oeuvre particulière pour être renvoyé dans des registres (ne dépassant pas un mot machine, ou remplissant deux mots machine précisément) ayant un retour de fonction, la structure peut être plus rapide que de passer l'adresse d'une structure, en particulier car exposer l'adresse d'une variable à un code extérieur susceptible de conserver une copie de celle-ci pourrait empêcher certaines optimisations utiles. Si un type ne satisfait pas à ces exigences, le code généré pour une fonction qui retourne une structure sera similaire à celui d'une fonction qui accepte un pointeur de destination. le code appelant serait probablement plus rapide pour le formulaire utilisant un pointeur, mais ce formulaire perdrait certaines opportunités d'optimisation.
C’est dommage que C ne fournisse pas le moyen de dire qu’il est interdit à une fonction de conserver une copie d’un pointeur passé (une sémantique semblable à une référence C ++), car le passage d’un pointeur aussi restreint serait un avantage direct en termes de performances. un pointeur sur un objet préexistant, tout en évitant les coûts sémantiques liés à l'obligation pour un compilateur de considérer l'adresse d'une variable comme "exposée".
la source
Un argument en faveur du style "paramètre de sortie" est qu'il permet à la fonction de renvoyer un code d'erreur.
Si vous considérez un ensemble de structures associées, si l'initialisation peut échouer pour l'une d'entre elles, il peut être intéressant que toutes utilisent le style de paramètre sortant pour des raisons de cohérence.
la source
errno
.Je présume que vous vous concentrez sur l'initialisation via le paramètre de sortie plutôt que sur l'initialisation via le retour, et non sur la divergence dans la manière dont les arguments de construction sont fournis.
Notez que la première approche pourrait permettre
Foo
d’être opaque (mais pas avec la manière dont vous l’utilisez actuellement), ce qui est généralement souhaitable pour la maintenabilité à long terme. Vous pouvez par exemple envisager une fonction qui alloue uneFoo
structure opaque sans l'initialiser. Ou peut-être avez-vous besoin de réinitialiser uneFoo
structure qui avait été précédemment initialisée avec différentes valeurs.la source