web-dev-qa-db-fra.com

Initialisation pratique de la structure C ++

J'essaie de trouver un moyen pratique d'initialiser les structures C++ 'pod'. Maintenant, considérons la structure suivante:

struct FooBar {
  int foo;
  float bar;
};
// just to make all examples work in C and C++:
typedef struct FooBar FooBar;

Si je veux facilement l'initialiser en C (!), Je pourrais simplement écrire:

/* A */ FooBar fb = { .foo = 12, .bar = 3.4 }; // illegal C++, legal C

Notez que je veux explicitement éviter la notation suivante, car il me semble que je dois me casser le cou si je change quoi que ce soit dans la structure à l'avenir. :

/* B */ FooBar fb = { 12, 3.4 }; // legal C++, legal C, bad style?

Pour obtenir le même (ou au moins similaire) en C++ comme dans le /* A */ _ exemple, je devrais implémenter un constructeur idiot:

FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar) {}
// ->
/* C */ FooBar fb(12, 3.4);

Ce qui est bon pour faire bouillir de l'eau, mais ne convient pas aux paresseux (la paresse est une bonne chose, non?). En outre, il est à peu près aussi mauvais que le /* B */ exemple, car il n’indique pas explicitement quelle valeur va à quel membre.

Ma question est donc de savoir comment je peux réaliser quelque chose de similaire à /* A */ ou mieux en C++? Autrement, je serais prêt à expliquer pourquoi je ne devrais pas vouloir faire cela (c’est-à-dire pourquoi mon paradigme mental est mauvais).

EDIT

Par pratique , j'entends aussi maintenable et non redondant .

136
bitmask

Les initiales désignées seront prises en charge dans c ++ 2a, mais vous n’attendrez pas, car elles sont officiellement prises en charge par GCC, Clang et MSVC.

#include <iostream>
#include <filesystem>

struct hello_world {
    const char* hello;
    const char* world;
};

int main () 
{
    hello_world hw = {
        .hello = "hello, ",
        .world = "world!"
    };

    std::cout << hw.hello << hw.world << std::endl;
    return 0;
}

GCC DemoMSVC Demo

14
ivaigult

Puisque style A n'est pas autorisé en C++ et vous ne voulez pas que style B alors que diriez-vous d'utiliser style BX:

FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 };  // :)

Au moins aider dans une certaine mesure.

38
iammilind

Vous pouvez utiliser un lambda:

const FooBar fb = [&] {
    FooBar fb;
    fb.foo = 12;
    fb.bar = 3.4;
    return fb;
}();

Vous trouverez plus d'informations sur cet idiome sur blog de Herb Sutter .

8
eyelash

Votre question est un peu difficile parce que même la fonction:

static FooBar MakeFooBar(int foo, float bar);

peut être appelé comme:

FooBar fb = MakeFooBar(3.4, 5);

en raison des règles de promotion et de conversion pour les types numériques intégrés. (C n'a jamais été très typé)

En C++, ce que vous voulez est réalisable, bien qu'avec l'aide de modèles et d'assertions statiques:

template <typename Integer, typename Real>
FooBar MakeFooBar(Integer foo, Real bar) {
  static_assert(std::is_same<Integer, int>::value, "foo should be of type int");
  static_assert(std::is_same<Real, float>::value, "bar should be of type float");
  return { foo, bar };
}

En C, vous pouvez nommer les paramètres, mais vous n'irez jamais plus loin.

D'autre part, si tout ce que vous voulez est nommé paramètres, alors vous écrivez beaucoup de code encombrant:

struct FooBarMaker {
  FooBarMaker(int f): _f(f) {}
  FooBar Bar(float b) const { return FooBar(_f, b); }
  int _f;
};

static FooBarMaker Foo(int f) { return FooBarMaker(f); }

// Usage
FooBar fb = Foo(5).Bar(3.4);

Et vous pouvez utiliser la protection de type promotion si vous le souhaitez.

8
Matthieu M.

Extrayez les contants dans les fonctions qui les décrivent (refactoring de base):

FooBar fb = { foo(), bar() };

Je sais que le style est très proche de celui que vous ne vouliez pas utiliser, mais il permet de remplacer plus facilement les valeurs constantes et de les expliquer également (sans avoir besoin de modifier les commentaires), si jamais elles étaient modifiées.

Une autre chose que vous pouvez faire (puisque vous êtes paresseux) est de rendre le constructeur en ligne, de sorte que vous n’ayez pas à taper autant (en supprimant "Foobar ::" et le temps passé à basculer entre les fichiers hpp et cpp):

struct FooBar {
  FooBar(int f, float b) : foo(f), bar(b) {}
  int foo;
  float bar;
};
7
ralphtheninja

Les interfaces C++ de nombreux compilateurs (y compris GCC et clang) comprennent la syntaxe de l'initialiseur C. Si vous le pouvez, utilisez simplement cette méthode.

5
Matthias Urlichs

Je sais que cette question est ancienne, mais il existe un moyen de résoudre ce problème jusqu'à ce que C++ 20 apporte enfin cette fonctionnalité de C à C++. Pour résoudre ce problème, utilisez des macros de préprocesseur avec static_asserts pour vérifier que votre initialisation est valide. (Je sais que les macros sont généralement mauvaises, mais ici, je ne vois pas d'autre moyen.) Voir l'exemple de code ci-dessous:

#define INVALID_STRUCT_ERROR "Instantiation of struct failed: Type, order or number of attributes is wrong."

#define CREATE_STRUCT_1(type, identifier, m_1, p_1) \
{ p_1 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_2(type, identifier, m_1, p_1, m_2, p_2) \
{ p_1, p_2 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\

#define CREATE_STRUCT_4(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3, m_4, p_4) \
{ p_1, p_2, p_3, p_4 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_4) >= (offsetof(type, m_3) + sizeof(identifier.m_3)), INVALID_STRUCT_ERROR);\

// Create more macros for structs with more attributes...

Ensuite, quand vous avez une structure avec des attributs const, vous pouvez faire ceci:

struct MyStruct
{
    const int attr1;
    const float attr2;
    const double attr3;
};

const MyStruct test = CREATE_STRUCT_3(MyStruct, test, attr1, 1, attr2, 2.f, attr3, 3.);

C'est un peu gênant, car vous avez besoin de macros pour chaque nombre d'attributs possible et vous devez répéter le type et le nom de votre instance dans l'appel de macro. De plus, vous ne pouvez pas utiliser la macro dans une instruction return, car les assertions viennent après l'initialisation.

Mais cela résout votre problème: lorsque vous modifiez la structure, l'appel échouera au moment de la compilation.

Si vous utilisez C++ 17, vous pouvez même rendre ces macros plus strictes en forçant les mêmes types, par exemple:

#define CREATE_STRUCT_3(type, identifier, m_1, p_1, m_2, p_2, m_3, p_3) \
{ p_1, p_2, p_3 };\
static_assert(offsetof(type, m_1) == 0, INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_2) >= sizeof(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(offsetof(type, m_3) >= (offsetof(type, m_2) + sizeof(identifier.m_2)), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_1) == typeid(identifier.m_1), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_2) == typeid(identifier.m_2), INVALID_STRUCT_ERROR);\
static_assert(typeid(p_3) == typeid(identifier.m_3), INVALID_STRUCT_ERROR);\
2
Max Vollmer

Encore une autre manière en C++ est

struct Point
{
private:

 int x;
 int y;

public:
    Point& setX(int xIn) { x = Xin; return *this;}
    Point& setY(int yIn) { y = Yin; return *this;}

}

Point pt;
pt.setX(20).setY(20);
2

Option D:

FooBar FooBarMake(int foo, float bar)

C légal, C++ juridique. Facilement optimisable pour les POD. Bien sûr, il n'y a pas d'argument nommé, mais cela ressemble à tout C++. Si vous voulez des arguments nommés, Objective C devrait être un meilleur choix.

Option E:

FooBar fb;
memset(&fb, 0, sizeof(FooBar));
fb.foo = 4;
fb.bar = 15.5f;

C légal, C++ juridique. Arguments nommés.

2
John

Le chemin /* B */ est très bien en C++, le C++ 0x va étendre la syntaxe, ce qui est également utile pour les conteneurs C++. Je ne comprends pas pourquoi vous appelez ça du mauvais style?

Si vous souhaitez indiquer des paramètres avec des noms, vous pouvez utiliser bibliothèque de paramètres boost , mais vous risquez de confondre une personne inconnue.

La réorganisation des membres de la structure est semblable à la réorganisation des paramètres de la fonction. Un tel refactoring peut poser problème si vous ne le faites pas très attentivement.

1
Öö Tiib

Qu'en est-il de cette syntaxe?

typedef struct
{
    int a;
    short b;
}
ABCD;

ABCD abc = { abc.a = 5, abc.b = 7 };

Vient de tester sur Microsoft Visual C++ 2015 et sur g ++ 6.0.2. Travailler bien.
Vous pouvez également créer une macro spécifique si vous souhaitez éviter la duplication du nom de la variable.

0
deselect

Pour moi, le moyen le plus simple d'autoriser l'initialisation en ligne est d'utiliser cette macro.

#define METHOD_MEMBER(TYPE, NAME, CLASS) \
CLASS &set_ ## NAME(const TYPE &_val) { NAME = _val; return *this; } \
TYPE NAME;

struct foo {
    METHOD_MEMBER(string, attr1, foo)
    METHOD_MEMBER(int, attr2, foo)
    METHOD_MEMBER(double, attr3, foo)
};

// inline usage
foo test = foo().set_attr1("hi").set_attr2(22).set_attr3(3.14);

Cette macro crée un attribut et une méthode d’auto-référence.

0
Emanuele Pavanello