Удобная инициализация структуры С++
Я пытаюсь найти удобный способ для инициализации '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);
Этот макрос создает атрибут и метод собственной ссылки.