Инициализация структуры C++
Можно ли инициализировать структуры в C++, как указано ниже
struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
address temp_address =
{ .city = "Hamilton", .prov = "Ontario" };
Ссылки здесь и здесь упоминают, что этот стиль можно использовать только в C. Если так, то почему это невозможно в C++? Есть ли какая-либо техническая причина, почему он не реализован в C++, или это плохая практика использовать этот стиль. Мне нравится использовать этот способ инициализации, потому что моя структура большая, и этот стиль дает мне четкую читаемость того, какое значение назначено какому-либо члену.
Пожалуйста, поделитесь со мной, если есть другие способы, с помощью которых мы можем достичь той же читабельности.
Я ссылался на следующие ссылки, прежде чем отправлять этот вопрос
18 ответов
Если вы хотите прояснить, что представляет собой каждое значение инициализатора, просто разбейте его на несколько строк с комментарием к каждой:
address temp_addres = {
0, // street_no
nullptr, // street_name
"Hamilton", // city
"Ontario", // prov
nullptr, // postal_code
};
После того, как мой вопрос не привел к удовлетворительному результату (потому что C++ не реализует основанный на тегах init для структур), я принял хитрость, которую я нашел здесь: члены структуры C++ инициализируются в 0 по умолчанию?
Для вас это будет так:
address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";
Это, безусловно, ближе всего к тому, что вы хотели изначально (обнулите все поля, кроме тех, которые вы хотите инициализировать).
Как уже упоминали другие, это обозначается как инициализатор.
Эта функция является частью C++20
Идентификаторы полей действительно являются синтаксисом инициализатора Си. В C++ просто дайте значения в правильном порядке без имен полей. К сожалению, это означает, что вам нужно дать их все (на самом деле вы можете не указывать конечные поля с нулевым значением, и результат будет таким же):
address temp_address = { 0, 0, "Hamilton", "Ontario", 0 };
Эта функция называется назначенными инициализаторами. Это дополнение к стандарту C99. Однако эта функция была исключена из C++ 11. В соответствии с языком программирования C++, 4-е издание, раздел 44.3.3.2 (функции C, не принятые в C++):
Несколько дополнений к C99 (по сравнению с C89) были намеренно не приняты в C++:
[1] Массивы переменной длины (VLA); использовать вектор или некоторую форму динамического массива
[2] Назначенные инициализаторы; использовать конструкторы
Грамматика C99 имеет назначенные инициализаторы [См. ИСО / МЭК 9899:2011, Проект комитета N1570 - 12 апреля 2011 г.]
6.7.9 Инициализация
initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designation_opt initializer
initializer-list , designationopt initializer
designation:
designator-list =
designator-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier
С другой стороны, C++ 11 не имеет назначенных инициализаторов [См. ИСО / МЭК 14882:2011, Проект комитета N3690 - 15 мая 2013 г.]
8.5 Инициализаторы
initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
initializer-clause:
assignment-expression
braced-init-list
initializer-list:
initializer-clause ...opt
initializer-list , initializer-clause ...opt
braced-init-list:
{ initializer-list ,opt }
{ }
Для достижения того же эффекта используйте конструкторы или списки инициализаторов:
Я знаю, что этот вопрос довольно старый, но я нашел другой способ инициализации, используя constexpr и curry:
struct mp_struct_t {
public:
constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
constexpr mp_struct_t another_member(int member) { return {member1, member, member2}; }
constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }
int member1, member2, member3;
};
static mp_struct_t a_struct = mp_struct_t{1}
.another_member(2)
.yet_another_one(3);
Этот метод также работает для глобальных статических переменных и даже для constexpr. Единственный недостаток - плохая ремонтопригодность: каждый раз, когда нужно сделать инициализируемым другой член с помощью этого метода, все методы инициализации члена должны быть изменены.
Вы можете просто инициализировать через ctor:
struct address {
address() : city("Hamilton"), prov("Ontario") {}
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
Я мог бы что-то упустить здесь, почему бы и нет:
#include <cstdio>
struct Group {
int x;
int y;
const char* s;
};
int main()
{
Group group {
.x = 1,
.y = 2,
.s = "Hello it works"
};
printf("%d, %d, %s", group.x, group.y, group.s);
}
Вы даже можете упаковать решение Gui13 в один оператор инициализации:
struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
address ta = (ta = address(), ta.city = "Hamilton", ta.prov = "Ontario", ta);
Отказ от ответственности: я не рекомендую этот стиль
Это не реализовано в C++. (также, char*
строки? Надеюсь нет).
Обычно, если у вас так много параметров, это довольно серьезный запах кода. Но вместо этого, почему бы не просто инициализировать значение структуры и затем назначить каждому члену?
Вдохновлен этим действительно аккуратным ответом: ( /questions/43569618/pochemu-c11-ne-podderzhivaet-naznachennyie-spiski-initsializatorov-kak-c99/43569628#43569628)
Вы можете сделать закрытие ламбы:
// Nobody wants to remember the order of these things
struct SomeBigStruct {
int min = 1;
int mean = 3 ;
int mode = 5;
int max = 10;
string name;
string nickname;
... // the list goes on
}
,
class SomeClass {
static const inline SomeBigStruct voiceAmps = []{
ModulationTarget $ {};
$.min = 0;
$.nickname = "Bobby";
$.bloodtype = "O-";
return $;
}();
}
Или, если вы хотите быть очень модным
#define DesignatedInit(T, ...)\
[]{ T ${}; __VA_ARGS__; return $; }()
class SomeClass {
static const inline SomeBigStruct voiceAmps = DesignatedInit(
ModulationTarget,
$.min = 0,
$.nickname = "Bobby",
$.bloodtype = "O-",
);
}
С этим связаны некоторые недостатки, в основном связанные с неинициализированными членами. Судя по комментариям, содержащимся в связанных ответах, он компилируется эффективно, хотя я не проверял его.
В целом, я просто думаю, что это аккуратный подход.
В C++ инициализаторы в стиле C были заменены конструкторами, которые во время компиляции могут обеспечить выполнение только допустимых инициализаций (т. Е. После инициализации члены объекта являются согласованными).
Это хорошая практика, но иногда удобна предварительная инициализация, как в вашем примере. ООП решает это с помощью абстрактных классов или шаблонов творческого проектирования.
На мой взгляд, использование этого безопасного способа убивает простоту, и иногда компромисс в безопасности может быть слишком дорогим, поскольку простой код не нуждается в сложном дизайне, чтобы оставаться обслуживаемым.
В качестве альтернативного решения я предлагаю определить макросы с использованием лямбда-выражений, чтобы упростить инициализацию, чтобы она выглядела почти как в стиле C:
struct address {
int street_no;
const char *street_name;
const char *city;
const char *prov;
const char *postal_code;
};
#define ADDRESS_OPEN [] { address _={};
#define ADDRESS_CLOSE ; return _; }()
#define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE
Макрос ADDRESS расширяется до
[] { address _={}; /* definition... */ ; return _; }()
который создает и вызывает лямбду. Параметры макроса также разделяются запятыми, поэтому вам нужно заключить инициализатор в квадратные скобки и вызвать
address temp_address = ADDRESS(( _.city = "Hamilton", _.prov = "Ontario" ));
Вы также можете написать обобщенный инициализатор макроса
#define INIT_OPEN(type) [] { type _={};
#define INIT_CLOSE ; return _; }()
#define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE
но тогда звонок чуть менее красив
address temp_address = INIT(address,( _.city = "Hamilton", _.prov = "Ontario" ));
однако вы можете легко определить макрос ADDRESS, используя общий макрос INIT
#define ADDRESS(x) INIT(address,x)
Я нашел этот способ сделать это для глобальных переменных, который не требует изменения исходного определения структуры:
struct address {
int street_no;
char *street_name;
char *city;
char *prov;
char *postal_code;
};
затем объявите переменную нового типа, унаследованную от исходного типа структуры, и используйте конструктор для инициализации полей:
struct temp_address : address { temp_address() {
city = "Hamilton";
prov = "Ontario";
} } temp_address;
Не так элегантно, как стиль С, хотя...
Для локальной переменной тоже должно быть возможно, но нужно проверить, нужен ли тогда дополнительный memset(this, 0, sizeof(*this))...
(Обратите внимание, что "temp_address" является переменной типа "temp_address", однако этот новый тип наследуется от "адреса" и может использоваться в любом месте, где ожидается "адрес", так что все в порядке.)
У вас есть
Стандартный список инициализации
address temp_address { /* street_no */, /* street_name */, ... /* postal_code */ }; address temp_address2 = { /* street_no */, /* street_name */, ... /* postal_code */ }
Точечная нотация
address temp_address; temp_address.street_no = ...; temp_address.street_name = ...; ... temp_address.postal_code = ...;
Назначенная совокупная инициализация, где список инициализации содержит метки каждого члена структуры (см. документацию ), доступные начиная с C++20.
Лечение
struct
как класс C++ - в C++ структуры на самом деле являются специальными типами классов, где все члены (в отличие от стандартного класса C++, где все членыprivate
если не указано иное явно), а также то, что при использовании наследования они по умолчаниюpublic
:struct Address { int street_no; ... char* postal_code; Address (int _street_no, ... , char* _postal_code) : street_no(_street_no), ... postal_code(_postal_code) {} } ... Address temp_address ( /* street_no */, ..., /* postal_code */);
Когда дело доходит до того, как вы инициализируете свою структуру, вы должны учитывать следующие аспекты:
- Переносимость - разные компиляторы, разная степень полноты стандарта С++ и разные стандарты С++ в целом ограничивают ваши возможности. Если вам приходится работать, скажем, с компилятором С++ 11, но вы хотите использовать обозначенную агрегатную инициализацию С++ 20, вам не повезло
- Читаемость - что читабельнее:
temp_address.city = "Toronto"
или жеtemp_address { ..., "Toronto", ... }
? Читабельность вашего кода очень важна. Особенно, когда у вас есть большие структуры (хуже - вложенные), наличие непомеченных значений повсюду просто напрашивается на неприятности. - Масштабируемость - все, что зависит от конкретного заказа, не является хорошей идеей. То же самое и с отсутствием этикеток. Вы хотите переместить член вверх или вниз по адресному пространству структуры? Удачи со списком инициализации без меток (охота за переставленными значениями при инициализации структуры — кошмар)... Вы хотите добавить нового члена? Еще раз удачи во всем, что зависит от конкретного заказа.
Хотя точечная нотация означает, что вы печатаете больше, преимущества, которые вы получаете от ее использования, перевешивают эту проблему, и поэтому я могу рекомендовать ее, если у вас нет небольшой структуры, ориентированной на будущее с точки зрения отсутствия изменений в ее структуре, и в этом случае вы может позволить себе использовать список инициализации. Помните: всякий раз, когда вы работаете с другими людьми, написание кода, которому легко следовать, имеет важное значение.
В GNUC++ (кажется, устарел с 2.5, давным-давно:) Смотрите ответы здесь: инициализация C struct с использованием меток. Это работает, но как?), можно инициализировать структуру следующим образом:
struct inventory_item {
int bananas;
int apples;
int pineapples;
};
inventory_item first_item = {
bananas: 2,
apples: 49,
pineapples: 4
};
Это возможно, но только если инициализируемая вами структура является структурой POD (обычные старые данные). Он не может содержать никаких методов, конструкторов или даже значений по умолчанию.
Я столкнулся с подобной проблемой сегодня, когда у меня есть структура, которую я хочу заполнить тестовыми данными, которые будут переданы в качестве аргументов функции, которую я тестирую. Я хотел иметь вектор этих структур и искал однострочный метод для инициализации каждой структуры.
Я закончил тем, что в структуре использовал функцию конструктора, которая, как я полагаю, была предложена в нескольких ответах на ваш вопрос.
Вероятно, это плохая практика, когда аргументы конструктора имеют те же имена, что и публичные переменные-члены, что требует использования this
указатель. Кто-то может предложить редактирование, если есть лучший способ.
typedef struct testdatum_s {
public:
std::string argument1;
std::string argument2;
std::string argument3;
std::string argument4;
int count;
testdatum_s (
std::string argument1,
std::string argument2,
std::string argument3,
std::string argument4,
int count)
{
this->rotation = argument1;
this->tstamp = argument2;
this->auth = argument3;
this->answer = argument4;
this->count = count;
}
} testdatum;
Который я использовал в своей тестовой функции для вызова тестируемой функции с различными аргументами, такими как этот:
std::vector<testdatum> testdata;
testdata.push_back(testdatum("val11", "val12", "val13", "val14", 5));
testdata.push_back(testdatum("val21", "val22", "val23", "val24", 1));
testdata.push_back(testdatum("val31", "val32", "val33", "val34", 7));
for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
}
Вы можете использовать extern "C" для компиляции кода на C в C++. В вашем случае вы можете использовать приведенный ниже код
extern "C" {
//write your C code which you want to compile in C++
struct address {
int street_no;
char *street_name;`enter code here`
char *city;
char *prov;
char *postal_code;
};
address temp_address ={ .city = "Hamilton", .prov = "Ontario" };
}