Я пытаюсь найти удобный способ инициализировать структуры C ++ «pod». Теперь рассмотрим следующую структуру:
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 ++? В качестве альтернативы, я был бы согласен с объяснением, почему я не должен этого делать (т.е. почему моя ментальная парадигма плохая).
РЕДАКТИРОВАТЬ
Под удобными я имею в виду также ремонтопригодные и неизбыточные .
источник
Ответы:
Назначенные инициализации будут поддерживаться в C ++ 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
источник
Поскольку
style A
это запрещено в C ++, и вы не хотите,style B
как насчет использованияstyle BX
:FooBar fb = { /*.foo=*/ 12, /*.bar=*/ 3.4 }; // :)
По крайней мере, хоть немного поможет.
источник
foo
иbar
в будущем. C по-прежнему будет инициализировать нужные поля, а C ++ - нет. И в этом суть вопроса - как добиться такого же результата на C ++. Я имею в виду, Python делает это с именованными аргументами, C - с именованными полями, и я надеюсь, что в C ++ должно что-то быть.explicit FooBar::FooBar(int foo, float bar) : foo(foo), bar(bar)
. Обратите внимание на явное ключевое слово. Даже нарушение стандарта лучше с точки зрения безопасности. In Clang: -Wno-c99-extensionsВы можете использовать лямбду:
const FooBar fb = [&] { FooBar fb; fb.foo = 12; fb.bar = 3.4; return fb; }();
Более подробную информацию об этой идиоме можно найти в блоге Херба Саттера .
источник
fb.XXX = YYY
.Ваш вопрос несколько сложен, потому что даже функция:
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);
И вы можете перчить в защите продвижения типа, если хотите.
источник
Извлеките конанты в функции, которые их описывают (базовый рефакторинг):
Я знаю, что этот стиль очень близок к тому, который вы не хотели использовать, но он позволяет упростить замену постоянных значений, а также объяснить их (таким образом, не нужно редактировать комментарии), если они когда-либо изменятся.
Еще одна вещь, которую вы могли бы сделать (поскольку вы ленивы), - это сделать конструктор встроенным, чтобы вам не приходилось много печатать (удаление «Foobar ::» и время, затрачиваемое на переключение между h и cpp файлом):
struct FooBar { FooBar(int f, float b) : foo(f), bar(b) {} int foo; float bar; };
источник
Многие внешние интерфейсы C ++ компиляторов (включая GCC и clang) понимают синтаксис инициализатора C. Если можете, просто воспользуйтесь этим методом.
источник
private: FooBar(float x, int y) {};
Еще один способ в 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);
источник
inline
!Вариант 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 ++. Именованные аргументы.
источник
FooBar fb = {};
в C ++, он по умолчанию инициализирует все члены структуры.Я знаю, что это старый вопрос, но есть способ решить его, пока 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);\
источник
Способ
/* B */
хорош в C ++, также C ++ 0x расширяет синтаксис, поэтому он также полезен для контейнеров C ++. Я не понимаю, почему вы называете это дурным стилем?Если вы хотите указать параметры с именами, вы можете использовать библиотеку параметров boost , но это может запутать кого-то, кто с ней не знаком.
Переупорядочивание элементов структуры похоже на переупорядочивание параметров функции, такой рефакторинг может вызвать проблемы, если вы не сделаете его очень осторожно.
источник
А как насчет этого синтаксиса?
typedef struct { int a; short b; } ABCD; ABCD abc = { abc.a = 5, abc.b = 7 };
Только что протестировано на Microsoft Visual C ++ 2015 и на g ++ 6.0.2. Работает нормально.
Вы также можете создать конкретный макрос, если хотите избежать дублирования имени переменной.
источник
clang++
3.5.0-10,-Weverything -std=c++1z
похоже, подтверждает это. Но это не выглядит правильным. Вы знаете, где стандарт подтверждает, что это действительный C ++?ABCD abc = { abc.b = 7, abc.a = 5 };
.Для меня самый ленивый способ разрешить встроенную инициализацию - использовать этот макрос.
#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);
Этот макрос создает атрибут и метод ссылки на себя.
источник
Для версий C ++ до C ++ 20 (которые вводят именованную инициализацию, что делает ваш вариант A действительным в C ++), примите во внимание следующее:
int main() { struct TFoo { int val; }; struct TBar { float val; }; struct FooBar { TFoo foo; TBar bar; }; FooBar mystruct = { TFoo{12}, TBar{3.4} }; std::cout << "foo = " << mystruct.foo.val << " bar = " << mystruct.bar.val << std::endl; }
Обратите внимание: если вы попытаетесь инициализировать структуру с помощью
FooBar mystruct = { TFoo{12}, TFoo{3.4} };
вы получите ошибку компиляции.Обратной стороной является то, что вам нужно создать одну дополнительную структуру для каждой переменной внутри вашей основной структуры, а также вы должны использовать внутреннее значение с
mystruct.foo.val
. Но с другой стороны, он чистый, простой, чистый и стандартный.источник