Initialisation de la structure C ++

279

Est-il possible d'initialiser des structures en C ++ comme indiqué ci-dessous

struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city = "Hamilton", .prov = "Ontario" };

Les liens ici et ici mentionnent qu'il est possible d'utiliser ce style uniquement en C. Si oui, pourquoi cela n'est-il pas possible en C ++? Y a-t-il une raison technique sous-jacente pour laquelle il n'est pas implémenté en C ++, ou est-ce une mauvaise pratique d'utiliser ce style. J'aime utiliser cette façon d'initialiser car ma structure est grande et ce style me donne une lisibilité claire de quelle valeur est affectée à quel membre.

Veuillez partager avec moi s'il existe d'autres moyens par lesquels nous pouvons atteindre la même lisibilité.

J'ai référé les liens suivants avant de poster cette question

  1. C / C ++ pour AIX
  2. Initialisation de la structure C avec variable
  3. Initialisation de la structure statique avec des balises en C ++
  4. Initialisation de la structure appropriée de C ++ 11
Dinesh PR
la source
20
Vue personnelle du monde: vous n'avez pas besoin de ce style d'initialisation d'objet en C ++ car vous devriez plutôt utiliser un constructeur.
Philip Kendall
7
Oui, j'y ai pensé, mais j'ai un large éventail de grandes structures. Il serait facile et lisible pour moi d'utiliser cette méthode. Avez-vous un style / une bonne pratique d'initialisation à l'aide de Constructor qui offre également une meilleure lisibilité.
Dinesh PR
2
Avez-vous envisagé la bibliothèque de paramètres boost, en combinaison avec des constructeurs? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy
18
Pas tellement lié à la programmation: cette adresse fonctionne très bien aux États-Unis uniquement. En France, nous n'avons pas de "province", dans d'autres parties du monde, il n'y a pas de code postal, la grand-mère d'un ami habite dans un si petit village que son adresse est "Ms X, code postal" nom du petit village "(oui, pas de rue). Alors, réfléchissez bien à ce qu'est une adresse valide sur le marché à laquelle vous l'appliquerez;)
Matthieu M.
5
@MatthieuM. Il n'y a pas de provinces aux États-Unis (ce pourrait être un format canadien?), Mais il y a des États, des territoires et même de minuscules villages qui ne prennent pas la peine de nommer des rues. Ainsi, la question de la conformité des adresses s'applique même ici.
Tim

Réponses:

167

Si vous souhaitez clarifier la valeur de chaque initialiseur, divisez-la simplement sur plusieurs lignes, avec un commentaire sur chacune:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};
Wyzard
la source
7
Personnellement, j'aime et je recommande ce style
Dinesh PR
30
Quelle est la différence entre le faire et utiliser réellement la notation par points pour accéder PLUS PRÉCISEMENT au champ lui-même, ce n'est pas comme si vous économisez de l'espace si c'est ce qui vous préoccupe. Je n'ai vraiment pas de programmeurs C ++ quand il s'agit d'être cohérent et d'écrire du code maintenable, ils semblent toujours vouloir faire quelque chose de différent pour faire ressortir leur code, le code est censé refléter le problème résolu, il ne devrait pas être un idiome à part entière, viser la fiabilité et la facilité d'entretien.
5
@ user1043000 eh bien, pour commencer, dans ce cas, l'ordre dans lequel vous mettez vos membres est de la plus haute importance. Si vous ajoutez un champ au milieu de votre structure, vous devrez revenir à ce code et chercher l'endroit exact où insérer votre nouvelle initialisation, qui est difficile et ennuyeuse. Avec la notation par points, vous pouvez simplement mettre votre nouvelle initialisation à la fin de la liste sans vous soucier de la commande. Et la notation par points est bien plus sûre si vous ajoutez le même type (comme char*) que l'un des autres membres au-dessus ou en dessous de la structure, car il n'y a aucun risque de les échanger.
Gui13
6
Commentaire d'orip. Si la définition de la structure de données est modifiée et que personne ne pense à rechercher les initialisations, ou ne peut pas toutes les trouver, ou commet une erreur en les modifiant, les choses vont s'effondrer.
Edward Falk
3
La plupart (sinon la totalité) des structures POSIX n'ont pas d'ordre défini, uniquement des membres définis. (struct timeval){ .seconds = 0, .microseconds = 100 }sera toujours d'une centaine de microsecondes, mais timeval { 0, 100 }pourrait être d'une centaine de SECONDES . Vous ne voulez pas trouver quelque chose comme ça à la dure.
yyny
99

Après que ma question n'ait abouti à aucun résultat satisfaisant (car C ++ n'implémente pas d'init basé sur les balises pour les structures), j'ai pris l'astuce que j'ai trouvée ici: les membres d'une structure C ++ sont-ils initialisés à 0 par défaut?

Pour vous, cela reviendrait à le faire:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

C'est certainement le plus proche de ce que vous vouliez à l'origine (zéro tous les champs sauf ceux que vous souhaitez initialiser).

Gui13
la source
10
Cela ne fonctionne pas pour les objets statiquement non initialisés
user877329
5
static address temp_address = {};marchera. Le remplir ensuite dépend de l'exécution, oui. Vous pouvez contourner cela en fournissant une fonction statique qui ne init pour vous: static address temp_address = init_my_temp_address();.
Gui13
En C ++ 11, init_my_temp_addresspeut être une fonction lambda:static address temp_address = [] () { /* initialization code */ }();
dureuill
3
Mauvaise idée, elle viole le principe RAII.
hxpax
2
Vraiment mauvaise idée: ajoutez un membre à votre addresset vous ne saurez jamais tous les endroits qui créent un addresset maintenant n'initialisez pas votre nouveau membre.
mystery_doctor
25

Comme d'autres l'ont mentionné, il s'agit d'un initialiseur désigné.

Cette fonctionnalité fait partie de C ++ 20

sameer chaudhari
la source
17

Les identifiants de champ sont en effet la syntaxe d'initialisation C. En C ++, donnez simplement les valeurs dans le bon ordre sans les noms de champs. Malheureusement, cela signifie que vous devez tous les donner (en fait, vous pouvez omettre les champs à valeur nulle de fin et le résultat sera le même):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 
Gène
la source
1
Oui, vous pouvez toujours utiliser l'initialisation de la structure alignée.
texasbruce
3
Oui, actuellement j'utilise uniquement cette méthode (Initialisation de la structure alignée). Mais je pense que la lisibilité n'est pas bonne. Puisque ma structure est grande, l'initialiseur a tellement de données et il est difficile pour moi de suivre quelle valeur est affectée à quel membre.
Dinesh PR
7
@ DineshP.R. Écrivez ensuite un constructeur!
M. Lister
2
@MrLister (ou n'importe qui) Peut-être que je suis coincé dans un nuage de stupidité en ce moment, mais voulez-vous expliquer comment un constructeur serait beaucoup mieux? Il me semble qu'il y a peu de différence entre fournir un tas de valeurs sans nom dépendant de l'ordre à une liste d'initialisation ou fournir un tas de valeurs sans nom dépendant de l'ordre à un constructeur ...?
yano
1
@yano Pour être honnête, je ne me souviens pas vraiment pourquoi je pensais qu'un constructeur serait la réponse au problème. Si je me souviens, je reviendrai vers vous.
M. Lister
13

Cette fonction est appelée initialiseurs désignés . Il s'agit d'un ajout à la norme C99. Cependant, cette fonctionnalité a été omise du C ++ 11. Selon le langage de programmation C ++, 4e édition, section 44.3.3.2 (Fonctionnalités C non adoptées par C ++):

Quelques ajouts à C99 (par rapport à C89) n'ont pas été délibérément adoptés en C ++:

[1] Tableaux de longueur variable (VLA); utiliser un vecteur ou une forme de tableau dynamique

[2] Initialiseurs désignés; utiliser des constructeurs

La grammaire C99 a les initialiseurs désignés [Voir ISO / IEC 9899: 2011, Projet de comité N1570 - 12 avril 2011]

6.7.9 Initialisation

initializer:
    assignment-expression
    { initializer-list }
    { initializer-list , }
initializer-list:
    designation_opt initializer
    initializer-list , designationopt initializer
designation:
    designator-list =
designator-list:
    designator
    designator-list designator
designator:
    [ constant-expression ]
    . identifier

D'un autre côté, le C ++ 11 n'a pas les initialiseurs désignés [Voir ISO / IEC 14882: 2011, Projet de comité N3690 - 15 mai 2013]

8.5 Initialiseurs

initializer:
    brace-or-equal-initializer
    ( expression-list )
brace-or-equal-initializer:
    = initializer-clause
    braced-init-list
initializer-clause:
    assignment-expression
    braced-init-list
initializer-list:
    initializer-clause ...opt
    initializer-list , initializer-clause ...opt
braced-init-list:
    { initializer-list ,opt }
    { }

Pour obtenir le même effet, utilisez des constructeurs ou des listes d'initialisation:

Guilherme Ferreira
la source
9

Vous pouvez simplement initialiser via ctor:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
nikc
la source
9
Ce n'est le cas que si vous contrôlez la définition de struct address. De plus, les types POD n'ont souvent intentionnellement ni constructeur ni destructeur.
user4815162342
7

Vous pouvez même regrouper la solution de Gui13 dans une seule déclaration d'initialisation:

struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };


address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);

Avertissement: je ne recommande pas ce style

user396672
la source
Ceci est toujours dangereux car il vous permet d'ajouter un membre addresset le code compilera toujours avec un million de places en initialisant uniquement les cinq membres d'origine. La meilleure partie de l'initialisation de la structure est que vous pouvez avoir tous les membres constet cela vous forcera à tous les initialiser
mystery_doctor
7

Je sais que cette question est assez ancienne, mais j'ai trouvé une autre façon d'initialiser, en utilisant constexpr et le curry:

struct mp_struct_t {
    public:
        constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
        constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
        constexpr mp_struct_t another_member(int member) { return {member1, member,     member2}; }
        constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

    int member1, member2, member3;
};

static mp_struct_t a_struct = mp_struct_t{1}
                           .another_member(2)
                           .yet_another_one(3);

Cette méthode fonctionne également pour les variables statiques globales et même celles constexpr. Le seul inconvénient est la mauvaise maintenabilité: chaque fois qu'un autre membre doit être initialisé à l'aide de cette méthode, toutes les méthodes d'initialisation des membres doivent être modifiées.

Fabien
la source
1
C'est le modèle du constructeur . Les méthodes membres peuvent renvoyer une référence à la propriété à modifier au lieu de créer une nouvelle structure à chaque fois
phuclv
6

Il se peut que je manque quelque chose ici, pourquoi pas:

#include <cstdio>    
struct Group {
    int x;
    int y;
    const char* s;
};

int main() 
{  
  Group group {
    .x = 1, 
    .y = 2, 
    .s = "Hello it works"
  };
  printf("%d, %d, %s", group.x, group.y, group.s);
}
run_the_race
la source
J'ai compilé le programme ci-dessus avec un compilateur MinGW C ++ et un compilateur Arduino AVR C ++, et les deux ont fonctionné comme prévu. Remarquez le #include <cstdio>
run_the_race
4
@run_the_race, il s'agit de ce que la norme c ++ indique et non du comportement d'un compilateur donné. Cependant, cette fonctionnalité arrive en c ++ 20.
Jon
cela ne fonctionne que si la structure est POD. Il cessera donc de compiler si vous lui ajoutez un constructeur.
oromoiluig
5

Il n'est pas implémenté en C ++. (aussi, des char*cordes? J'espère que non).

Habituellement, si vous avez autant de paramètres, c'est une odeur de code assez grave. Mais à la place, pourquoi ne pas simplement initialiser la structure par la valeur et ensuite affecter chaque membre?

Chiot
la source
6
"(aussi, des char*cordes? J'espère que non)." - Eh bien, c'est un exemple C.
Ed S.
ne pouvons-nous pas utiliser char * en C ++? Actuellement, je l'utilise et cela fonctionne (peut-être que je fais quelque chose de mal). Mon hypothèse est que le compilateur créera des chaînes constantes de "Hamilton" et "Ontario" et attribuera leur adresse aux membres de la structure. Sera-t-il correct d'utiliser à la place const char *?
Dinesh PR
8
Vous pouvez utiliser, char*mais le type const char*est beaucoup plus sûr et tout le monde utilise simplement std::stringparce qu'il est beaucoup plus fiable.
Puppy
D'accord. Quand j'ai lu "comme mentionné ci-dessous", j'ai supposé que c'était un exemple copié quelque part.
Ed S.
2

J'ai trouvé cette façon de le faire pour les variables globales, qui ne nécessite pas de modifier la définition de la structure d'origine:

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

puis déclarez la variable d'un nouveau type hérité du type struct d'origine et utilisez le constructeur pour l'initialisation des champs:

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

Pas aussi élégant que le style C ...

Pour une variable locale, elle nécessite un memset supplémentaire (this, 0, sizeof (* this)) au début du constructeur, donc ce n'est clairement pas pire et la réponse de @ gui13 est plus appropriée.

(Notez que 'temp_address' est une variable de type 'temp_address', cependant ce nouveau type hérite de 'address' et peut être utilisé partout où 'address' est attendu, donc c'est OK.)

VincentP
la source
1

J'ai rencontré un problème similaire aujourd'hui, où j'ai une structure que je veux remplir avec des données de test qui seront passées comme arguments à une fonction que je teste. Je voulais avoir un vecteur de ces structures et recherchais une méthode à une ligne pour initialiser chaque structure.

J'ai fini par utiliser une fonction constructeur dans la structure, qui, je crois, a également été suggérée dans quelques réponses à votre question.

C'est probablement une mauvaise pratique que les arguments du constructeur portent les mêmes noms que les variables de membre public, ce qui nécessite l'utilisation du thispointeur. Quelqu'un peut suggérer une modification s'il existe une meilleure méthode.

typedef struct testdatum_s {
    public:
    std::string argument1;
    std::string argument2;
    std::string argument3;
    std::string argument4;
    int count;

    testdatum_s (
        std::string argument1,
        std::string argument2,
        std::string argument3,
        std::string argument4,
        int count)
    {
        this->rotation = argument1;
        this->tstamp = argument2;
        this->auth = argument3;
        this->answer = argument4;
        this->count = count;
    }

} testdatum;

Dans lequel j'ai utilisé dans ma fonction de test pour appeler la fonction en cours de test avec divers arguments comme celui-ci:

std::vector<testdatum> testdata;

testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));

for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
    function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}
Unacoder
la source
1

C'est possible, mais seulement si la structure que vous initialisez est une structure POD (plain old data). Il ne peut contenir aucune méthode, constructeur ou même valeur par défaut.

qui sait
la source
1

En C ++, les initialiseurs de style C ont été remplacés par des constructeurs qui, au moment de la compilation, peuvent garantir que seules les initialisations valides sont effectuées (c'est-à-dire qu'après l'initialisation, les membres de l'objet sont cohérents).

C'est une bonne pratique, mais parfois une pré-initialisation est pratique, comme dans votre exemple. La POO résout ce problème par des classes abstraites ou des modèles de conception créatifs .

À mon avis, l'utilisation de cette méthode sécurisée tue la simplicité et parfois le compromis de sécurité peut être trop cher, car un code simple n'a pas besoin d'une conception sophistiquée pour rester maintenable.

Comme solution alternative, je suggère de définir des macros en utilisant des lambdas pour simplifier l'initialisation pour ressembler presque au style C:

struct address {
  int street_no;
  const char *street_name;
  const char *city;
  const char *prov;
  const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

La macro ADDRESS se développe pour

[] { address _={}; /* definition... */ ; return _; }()

qui crée et appelle le lambda. Les paramètres de macro sont également séparés par des virgules, vous devez donc mettre l'initialiseur entre crochets et appeler comme

address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));

Vous pouvez également écrire un initialiseur de macro généralisé

#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

mais alors l'appel est un peu moins beau

address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));

cependant, vous pouvez définir la macro ADRESSE à l'aide de la macro générale INIT facilement

#define ADDRESS(x) INIT(address,x)
Jan Turoň
la source
0

Inspiré par cette réponse vraiment soignée: ( https://stackoverflow.com/a/49572324/4808079 )

Vous pouvez faire des fermetures de lamba:

// Nobody wants to remember the order of these things
struct SomeBigStruct {
  int min = 1;
  int mean = 3 ;
  int mode = 5;
  int max = 10;
  string name;
  string nickname;
  ... // the list goes on
}

.

class SomeClass {
  static const inline SomeBigStruct voiceAmps = []{
    ModulationTarget $ {};
    $.min = 0;  
    $.nickname = "Bobby";
    $.bloodtype = "O-";
    return $;
  }();
}

Ou, si vous voulez être très chic

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

class SomeClass {
  static const inline SomeBigStruct voiceAmps = DesignatedInit(
    ModulationTarget,
    $.min = 0,
    $.nickname = "Bobby",
    $.bloodtype = "O-",
  );
}

Il y a quelques inconvénients à cela, principalement liés aux membres non initialisés. D'après les commentaires des réponses liées, il se compile efficacement, même si je ne l'ai pas testé.

Dans l'ensemble, je pense simplement que c'est une approche soignée.

Seph Reed
la source