Удобная инициализация структуры C ++ - PullRequest
135 голосов
/ 31 мая 2011

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

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?

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

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

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

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

РЕДАКТИРОВАТЬ

По удобно , я имею в виду также ремонтопригодно и не резервируется .

Ответы [ 12 ]

38 голосов
/ 31 мая 2011

Поскольку style A не разрешено в C ++, и вы не хотите style B, тогда как насчет использования style BX:

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

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

12 голосов
/ 23 августа 2018

Назначенные инициализации будут поддерживаться в c ++ 2a, но вам не нужно ждать, потому что они официально поддерживаются в GCC в Clang.

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

Демо

8 голосов
/ 18 марта 2018

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

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

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

8 голосов
/ 31 мая 2011

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

static FooBar MakeFooBar(int foo, float bar);

может называться:

FooBar fb = MakeFooBar(3.4, 5);

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

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

И если хотите, вы можете воспользоваться защитой повышения типа.

7 голосов
/ 31 мая 2011

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

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

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

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

struct FooBar {
  FooBar(int f, float b) : foo(f), bar(b) {}
  int foo;
  float bar;
};
5 голосов
/ 31 декабря 2012

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

2 голосов
/ 10 января 2018

Я знаю, что этот вопрос старый, но есть способ решить его, пока 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);\
2 голосов
/ 31 мая 2011

Вариант D:

FooBar FooBarMake(int foo, float bar)

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

Вариант E:

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

Правовой C, правовой C ++. Именованные аргументы.

2 голосов
/ 31 мая 2011

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

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);
1 голос
/ 31 мая 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...