Удобная инициализация структуры С++

Я пытаюсь найти удобный способ для инициализации 'pod' С++ structs. Теперь рассмотрим следующую структуру:

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

Если я хочу удобно инициализировать это в C (!), я мог бы просто написать:

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

Обратите внимание, что я хочу явно избегать следующих обозначений, потому что мне кажется, что мне нужно сломать шею, если я изменю что-либо в структуре в будущем:

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

Чтобы достичь того же (или, по крайней мере, подобного) в С++, как в примере /* A */, мне пришлось бы реализовать идиотский конструктор:

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

Это хорошо для кипящей воды, но не подходит для ленивых людей (лень - это хорошо, правда?). Кроме того, это очень плохо, как пример /* B */, поскольку он явно не указывает, какое значение переходит к тому элементу.

Итак, мой вопрос в основном заключается в том, как я могу достичь чего-то похожего на /* A */ или лучше на С++? В качестве альтернативы, я был бы в порядке с объяснением, почему я не должен этого делать (то есть, почему моя психическая парадигма плоха).

ИЗМЕНИТЬ

Удобно, я имею в виду также поддерживаемое и не избыточное.

Ответ 1

Назначенные инициализации будут поддерживаться в С++ 2a, но вам не нужно ждать, потому что они официально поддерживаются GCC, Clang и 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 Demo MSVC Demo

Ответ 2

Так как style A не разрешен в С++, и вы не хотите style B, то как насчет использования style BX:

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

По крайней мере, помогите в некоторой степени.

Ответ 3

Ваш вопрос несколько затруднен, потому что даже функция:

static FooBar MakeFooBar(int foo, float bar);

можно назвать следующим:

FooBar fb = MakeFooBar(3.4, 5);

из-за правил продвижения и конверсий для встроенных числовых типов. (C никогда не был действительно строго типизирован)

В С++ то, что вы хотите, возможно, но с помощью шаблонов и статических утверждений:

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

В C вы можете назвать параметры, но вы никогда не получите больше.

С другой стороны, если все, что вам нужно, это именованные параметры, тогда вы пишете много громоздкого кода:

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

И вы можете перцем в защите продвижения по типу, если хотите.

Ответ 4

Вы можете использовать лямбду:

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

Более подробную информацию об этой идиоме можно найти в блоге Херба Саттера.

Ответ 5

Извлеките контингенты в функции, которые их описывают (базовый рефакторинг):

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

Я знаю, что стиль очень близок к тому, который вы не хотели использовать, но он позволяет легче заменять постоянные значения, а также объяснять им (при этом не нужно редактировать комментарии), если они когда-либо меняются.

Еще одна вещь, которую вы могли бы сделать (поскольку вы ленивы) - сделать конструктор встроенным, поэтому вам не нужно вводить столько же (удаление "Foobar::" и время, затрачиваемое на переключение между h и cpp файлом):

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

Ответ 6

Интерфейсы C++ многих компиляторов (включая GCC и clang) понимают синтаксис инициализатора Си. Если вы можете, просто используйте этот метод.

Ответ 7

Еще один способ в С++ -

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

Ответ 8

Вариант D:

FooBar FooBarMake(int foo, float bar)

Юридический C, юридический С++. Легко оптимизируется для POD. Конечно, нет именованных аргументов, но это похоже на все С++. Если вам нужны именованные аргументы, Objective C должен быть лучшим выбором.

Вариант E:

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

Юридический C, юридический С++. Именованные аргументы.

Ответ 9

Я знаю, что этот вопрос старый, но есть способ решить его, пока C++ 20 наконец не перенесет эту функцию из C в C++. Чтобы решить эту проблему, используйте макросы препроцессора со static_asserts для проверки правильности инициализации. (Я знаю, что макросы обычно плохие, но здесь я не вижу другого пути.) Смотрите пример кода ниже:

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

Затем, когда у вас есть структура с атрибутами const, вы можете сделать это:

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++ 17, вы можете даже сделать эти макросы более строгими, применяя те же типы, например:

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

Ответ 10

Способ /* B */ отлично работает на С++, и С++ 0x расширяет синтаксис, поэтому он также полезен для контейнеров С++. Я не понимаю, почему вы называете это плохим стилем?

Если вы хотите указать параметры с именами, вы можете использовать библиотеку параметров форматирования, но это может смутить незнакомого с ней человека.

Переупорядочивание элементов структуры похоже на параметры функции переупорядочения, такой рефакторинг может вызвать проблемы, если вы не делаете это очень осторожно.

Ответ 11

Как насчет этого синтаксиса?

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

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

Просто протестирован на Microsoft Visual С++ 2015 и на g++ 6.0.2. Работа в порядке.
Вы также можете создать конкретный макрос, если хотите избежать дублирования имени переменной.

Ответ 12

Для меня самый ленивый способ разрешить встроенную инициализацию - использовать этот макрос.

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

Этот макрос создает атрибут и метод собственной ссылки.