it-swarm.com.de

Komfortable C ++ - Strukturinitialisierung

Ich versuche einen bequemen Weg zu finden, um 'pod' C++ - Strukturen zu initialisieren. Betrachten Sie nun die folgende Struktur:

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

Wenn ich dies bequem in C (!) Initialisieren möchte, könnte ich einfach schreiben:

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

Beachten Sie, dass ich die folgende Notation explizit vermeiden möchte, da sie mich dazu bringt, mir den Hals zu brechen, wenn ich irgendetwas in der Struktur in der Zukunft ändere:

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

In C++ dasselbe (oder zumindest ein ähnliches) Ergebnis erzielen wie in /* A */ Beispiel, ich müsste einen idiotischen Konstruktor implementieren:

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

Was zum Kochen von Wasser gut ist, aber nicht für faule Leute geeignet (Faulheit ist eine gute Sache, oder?). Außerdem ist es ziemlich so schlimm wie das /* B */ Beispiel, da nicht explizit angegeben wird, welcher Wert für welches Element gilt.

Meine Frage ist also im Grunde, wie ich etwas Ähnliches erreichen kann wie /* A */ oder besser in C++? Alternativ wäre ich mit einer Erklärung einverstanden, warum ich dies nicht tun sollte (d. H. Warum mein mentales Paradigma schlecht ist).

EDIT

Mit praktisch meine ich auch wartbar und nicht redundant.

136
bitmask

Ausgewiesene Initialisierungen werden in c ++ 2a unterstützt, aber Sie müssen nicht warten, da sie von GCC, Clang und MSVC offiziell unterstützt werden .

#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

Schon seit style A ist in C++ nicht erlaubt und Sie wollen nicht style B wie wäre es dann mit style BX:

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

Zumindest teilweise helfen.

38
iammilind

Sie könnten ein Lambda verwenden:

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

Weitere Informationen zu dieser Redewendung finden Sie auf Herb Sutters Blog .

8
eyelash

Deine Frage ist etwas schwierig, da schon die Funktion:

static FooBar MakeFooBar(int foo, float bar);

kann genannt werden als:

FooBar fb = MakeFooBar(3.4, 5);

aufgrund der Beförderungs- und Konvertierungsregeln für integrierte numerische Typen. (C wurde noch nie richtig stark getippt)

In C++ können Sie mit Hilfe von Vorlagen und statischen Zusicherungen die gewünschten Ergebnisse erzielen:

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 };
}

In C können Sie die Parameter benennen, aber Sie werden nie weiter kommen.

Wenn Sie jedoch nur Parameter benennen möchten, schreiben Sie viel umständlichen Code:

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);

Und wenn Sie möchten, können Sie den Typ-Promotion-Schutz verwenden.

8
Matthieu M.

Extrahieren Sie die Inhaltsstoffe in Funktionen, die sie beschreiben (grundlegendes Refactoring):

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

Ich weiß, dass der Stil dem sehr nahe kommt, den Sie nicht verwenden wollten, aber er ermöglicht das einfachere Ersetzen der konstanten Werte und erklärt sie auch (so dass keine Kommentare bearbeitet werden müssen), falls sie sich jemals ändern sollten.

Eine andere Möglichkeit (da Sie faul sind) besteht darin, den Konstruktor inline zu machen, damit Sie nicht so viel eingeben müssen (Entfernen von "Foobar ::" und Zeitaufwand für den Wechsel zwischen h- und cpp-Datei):

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

Die C++ - Frontends vieler Compiler (einschließlich GCC und Clang) verstehen die C-Initialisierungssyntax. Wenn Sie können, verwenden Sie einfach diese Methode.

5

Ich weiß, dass diese Frage alt ist, aber es gibt einen Weg, dies zu lösen, bis C++ 20 diese Funktion endlich von C auf C++ bringt. Um dies zu lösen, können Sie Präprozessor-Makros mit static_asserts verwenden, um zu überprüfen, ob Ihre Initialisierung gültig ist. (Ich weiß, dass Makros im Allgemeinen schlecht sind, aber hier sehe ich keinen anderen Weg.) Siehe folgenden Beispielcode:

#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...

Wenn Sie dann eine Struktur mit const-Attributen haben, können Sie dies tun:

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.);

Dies ist etwas umständlich, da Sie für jede mögliche Anzahl von Attributen Makros benötigen und den Typ und den Namen Ihrer Instanz im Makroaufruf wiederholen müssen. Sie können das Makro auch nicht in einer return-Anweisung verwenden, da die Asserts nach der Initialisierung kommen.

Aber es löst Ihr Problem: Wenn Sie die Struktur ändern, schlägt der Aufruf zur Kompilierungszeit fehl.

Wenn Sie C++ 17 verwenden, können Sie diese Makros sogar verschärfen, indem Sie dieselben Typen erzwingen, z.

#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

Ein weiterer Weg in C++ ist

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)

Legal C, legal C++. Einfach für PODs zu optimieren. Natürlich gibt es keine benannten Argumente, aber das ist wie bei jedem C++. Wenn Sie benannte Argumente wünschen, sollte Ziel C die bessere Wahl sein.

Option E:

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

Legal C, legal C++. Benannte Argumente.

2
John

Der Weg /* B */ ist in C++ in Ordnung, auch C++ 0x wird die Syntax erweitern, so dass es auch für C++ - Container nützlich ist. Ich verstehe nicht, warum du es schlechten Stil nennst?

Wenn Sie Parameter mit Namen kennzeichnen möchten, können Sie Boost-Parameterbibliothek verwenden, dies kann jedoch jemanden verwirren, der mit diesen nicht vertraut ist.

Das Umordnen von Strukturelementen entspricht dem Umordnen von Funktionsparametern. Ein solches Umgestalten kann Probleme verursachen, wenn Sie es nicht sehr sorgfältig ausführen.

1
Öö Tiib

Was ist mit dieser Syntax?

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

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

Gerade auf einem Microsoft Visual C++ 2015 und auf g ++ 6.0.2 getestet. OK arbeiten.
Sie können auch ein bestimmtes Makro erstellen, wenn Sie vermeiden möchten, dass der Variablenname doppelt vorkommt.

0
deselect

Für mich ist die faulste Möglichkeit, Inline-Inizialisierung zuzulassen, die Verwendung dieses Makros.

#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);

Dieses Makro erstellt ein Attribut und eine Selbstreferenzmethode.

0