Ініціалізація структури C ++


279

Чи можливо ініціалізувати структури в 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 ++, або погана практика використання цього стилю. Мені подобається використовувати такий спосіб ініціалізації, тому що моя структура є великою, і цей стиль дає мені чітку зрозумілість того, яке значення присвоюється члену.

Будь ласка, поділіться зі мною, якщо є інші способи, завдяки яким ми можемо досягти такої самої читабельності.

Я надсилав наступні посилання, перш ніж публікувати це питання

  1. C / C ++ для AIX
  2. C Ініціалізація структури зі змінною
  3. Ініціалізація статичної структури тегами в C ++
  4. C ++ 11 Ініціалізація належної структури

20
Особистий погляд на світ: цей стиль ініціалізації об'єктів у C ++ вам не потрібен, оскільки натомість ви повинні використовувати конструктор.
Філіп Кендалл

7
Так, я думав про це, але у мене є масив великої Структури. Мені було б легко та легко читати цей спосіб. Чи є у вас стиль / хороша практика ініціалізації за допомогою Constructor, що також дає кращу читабельність.
PR Дінеш

2
Ви розглядали бібліотеку параметрів boost у поєднанні з конструкторами? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Тоні Делрой

18
Не так пов’язано програмування: ця адреса працює чудово лише у США. У Франції у нас немає "провінції", в інших куточках світу немає поштового індексу, бабуся матері друга живе в такому маленькому селі, що її адреса "пані X, поштовий індекс мала-село-назва "(так, вулиця немає). Тож уважно подумайте, якою є дійсною адресою до ринку, на яку ви застосуєте це;)
Матьє М.

5
@MatthieuM. У США немає провінцій (це може бути канадський формат?), Але є штати, території і навіть крихітні села, які не намагаються назвати вулиці. Тож питання відповідності адреси стосується навіть тут.
Тім

Відповіді:


166

Якщо ви хочете уточнити, що таке значення кожного ініціалізатора, просто розділіть його на кілька рядків з коментарем до кожного:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};

7
Мені особисто подобається і рекомендую цей стиль
Dinesh PR

30
Яка різниця між тим, як це зробити, і власне використанням крапкових позначень для доступу до БІЛЬШЕ ТОЧНО до самого поля, це не так, як ви економите будь-який простір, якщо саме це викликає занепокоєння. Я дійсно не отримую програмістів на C ++, коли справа доходить до послідовності та написання постійного коду, вони, здається, завжди хочуть зробити щось інше, щоб їх код виділявся, код призначений для відображення проблеми, яку вирішують. ідіома сама по собі, спрямована на надійність та простоту обслуговування.

5
@ user1043000 добре, для одного, у цьому випадку порядок, яким ви ставите своїх членів, має найважливіше значення. Якщо ви додасте поле в середині вашої структури, вам доведеться повернутися до цього коду і шукати точне місце, куди потрібно вставити нову ініціалізацію, що важко і нудно. За допомогою позначення крапок ви можете просто поставити нову ініціалізацію в кінці списку, не турбуючись із замовленням. А крапка нотацій є набагато безпечнішою, якщо вам трапиться в структурі додати той же тип (наприклад char*), як один з інших членів вище чи нижче, оскільки немає ризику їх заміни.
Gui13

6
коментар Оріпа. Якщо визначення структури даних змінюється, і ніхто не думає шукати ініціалізації, або не може їх знайти всіх, або помилився з їх редагуванням, речі розпадуться.
Едвард Фолк

3
Більшість (якщо не всі) структури POSIX не мають визначеного порядку, лише визначені члени. (struct timeval){ .seconds = 0, .microseconds = 100 }завжди буде сотню мікросекунд, але timeval { 0, 100 }може бути і сто секунд . Ви не хочете знайти щось подібне на важкому шляху.
yyny

99

Після того, як моє запитання не призвело до задоволення результату (оскільки C ++ не реалізує init на основі тегів для структур), я взяв фокус, який я знайшов тут: Чи члени структури C ++ ініціалізовані до 0 за замовчуванням?

Для вас було б зробити це:

address temp_address = {}; // will zero all fields in C++
temp_address.city = "Hamilton";
temp_address.prov = "Ontario";

Це, звичайно, найближче до того, що ви хотіли спочатку (нульові всі поля, крім тих, які ви хочете ініціалізувати).


10
Це не працює для статично ініціалізованих об'єктів
user877329

5
static address temp_address = {};буду працювати. Після цього заповнити його - до часу виконання. Ви можете обійти це, надаючи функцію статичної , яка робить ініціалізації для вас: static address temp_address = init_my_temp_address();.
Gui13

У C ++ 11 init_my_temp_addressможе бути функція лямбда:static address temp_address = [] () { /* initialization code */ }();
dureuill

3
Погана ідея, вона порушує принцип RAII.
hxpax

2
Дійсно погана ідея: додайте одного члена до свого, addressі ви ніколи не дізнаєтесь про всі місця, які створюють, addressі тепер не ініціалізуйте свого нового члена.
таємниця_доктор


17

Ідентифікатори поля справді є синтаксисом ініціалізатора C. У C ++ просто наведіть значення у правильному порядку без назв полів. На жаль, це означає, що вам потрібно дати їх усім (насправді ви можете опустити проміжні поля з нульовим значенням, і результат буде однаковим):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 

1
Так, ви завжди можете використовувати ініціалізацію вирівняної структури.
texasbruce

3
Так, наразі я використовую лише цей метод (ініціалізація вирівняної структури). Але я вважаю, що читабельність не є хорошою. Оскільки моя структура є великою, у ініціалізатора є стільки даних, і мені важко відслідковувати, яке значення присвоюється якому члену.
PR Дінеш

7
@ DineshP.R. Тоді напишіть конструктор!
Містер Лістер

2
@MrLister (чи хтось) Можливо, на даний момент я застряг у хмарі дурості, але хочете пояснити, як конструктору було б набагато краще? Мені здається, є невелика різниця між наданням групи завдань, що не залежать від замовлення, неназваними значеннями до списку ініціалізаторів, або наданням конструктора, який має залежність від замовлення, неназваних значень ...?
яно

1
@yano Чесно кажучи, я не дуже пам'ятаю, чому я думав, що конструктор буде відповіддю на проблему. Якщо я пам’ятаю, я повернуся до вас.
Містер Лістер

13

Ця функція називається призначеними ініціалізаторами . Це доповнення до стандарту C99. Однак ця функція залишилась поза C ++ 11. Згідно з мовою програмування на C ++, 4-е видання, розділ 44.3.3.2 (Особливості C, не прийняті C ++):

Кілька доповнень до C99 (порівняно з C89) навмисно не було прийнято в C ++:

[1] Масиви змінної довжини (VLA); використовувати векторну чи якусь форму динамічного масиву

[2] Позначені ініціалізатори; використовувати конструктори

Граматика С99 має призначені ініціалізатори [Див. ISO / IEC 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 не має призначених ініціалізаторів [Див. ISO / IEC 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 }
    { }

Для досягнення того ж ефекту використовуйте конструктори або списки ініціалізаторів:


9

Ви можете просто ініціалізувати через ctor:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};

9
Це має місце лише в тому випадку, якщо ви контролюєте визначення struct address. Також типи POD часто навмисно не мають конструктора та деструктора.
user4815162342

7

Ви навіть можете упакувати рішення 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);

Відмова: Я не рекомендую цей стиль


Це все ще небезпечно, оскільки дозволяє додати учасника до addressкоду, а код все одно буде компілювати мільйон місць, лише ініціалізуючи початкові п'ять членів. Найкраща частина ініціалізації структури полягає в тому, що ви можете мати усіх членів, constі це змусить вас ініціалізувати їх усіх
mystery_doctor

7

Я знаю, що це питання досить старе, але я знайшов інший спосіб ініціалізації, використовуючи constexpr та currying:

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

Цей метод також працює для глобальних статичних змінних і навіть для contexpr. Єдиним недоліком є ​​погана ремонтопридатність: Кожен раз, коли інший член повинен бути ініціалізований за допомогою цього методу, всі методи ініціалізації членів повинні бути змінені.


1
Це закономірність будівельника . Методи-члени можуть повертати посилання на властивість, яку потрібно змінювати, а не створювати нову структуру кожного разу
phuclv

6

Я, можливо, щось тут не вистачає, чому б ні:

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

Я склав вищевказану програму з компілятором MinGW C ++ та компілятором Arduino AVR C ++, і обидва працювали як слід. Зверніть увагу на #include <cstdio>
run_the_race

4
@run_the_race, це те, що стандарт c ++ говорить не те, якою може бути поведінка даного компілятора. Однак ця функція виходить у c ++ 20.
Джон

це працює лише в тому випадку, якщо структура є POD. Тож він перестане компілювати, якщо до нього додати конструктор.
oromoiluig

5

Це не реалізовано в C ++. (також char*рядки? Сподіваюсь, що ні).

Зазвичай, якщо у вас стільки параметрів, це досить серйозний запах коду. Але натомість, чому б не просто ініціалізувати структуру і не призначити кожного члена?


6
"(також char*рядки? Сподіваюсь, що ні)." - Ну, це приклад С.
Ред С.

хіба ми не можемо використовувати char * в C ++? В даний час я його використовую, і він працює (можливо, я щось не так) Моє припущення, що компілятор створить постійні рядки "Гамільтон" і "Онтаріо" і призначить їх адресу членам структури. Чи правильно буде замість цього використовувати const char *?
Дінеш PR

8
Ви можете використовувати, char*але const char*це набагато більш безпечно для типів, і все просто використовують, std::stringоскільки це набагато надійніше.
Щеня

Гаразд. Коли я читав "як було сказано нижче", то припускав, що це був приклад, скопійований звідкись.
Ред С.

2

Я знайшов такий спосіб зробити це для глобальних змінних, який не потребує зміни оригінального визначення структури:

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;

Не настільки елегантний, як стиль C, хоча ...

Для локальної змінної вона потребує додаткового набору (це, 0, sizeof (* це)) на початку конструктора, тому це явно не гірше, і відповідь @ gui13 є більш доречною.

(Зауважте, що 'temp_address' є змінною типу 'temp_address', однак цей новий тип успадковується від 'address' і може використовуватися в будь-якому місці, де очікується 'address', тому це нормально.)


1

Я зіткнувся з подібною проблемою сьогодні, де у мене є структура, яку я хочу заповнити тестовими даними, які будуть передані як аргументи функції, яку я тестую. Я хотів мати вектор цих структур і шукав метод однолінійної ініціалізації кожної структури.

Я закінчила функцію конструктора в структурі, яка, на мою думку, також була запропонована в кількох відповідях на ваше запитання.

Мабуть, погана практика, щоб аргументи конструктору мали такі самі назви, як і загальнодоступні змінні, що вимагає використання 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);
}

1

Це можливо, але тільки якщо структура, яку ви ініціалізуєте, - це структура POD (звичайні старі дані). Він не може містити жодних методів, конструкторів або навіть значень за замовчуванням.


1

У C ++ ініціалізатори стилю C були замінені конструкторами, які за час компіляції можуть забезпечити виконання тільки дійсних ініціалізацій (тобто після ініціалізації члени об'єкта є послідовними).

Це хороша практика, але іноді попередня ініціалізація зручна, як у вашому прикладі. OOP вирішує це за допомогою абстрактних класів або креативних моделей дизайну .

На мою думку, використання цього безпечного способу вбиває простоту, а іноді компроміс із безпекою може бути надто дорогим, оскільки для простого коду не потрібен складний дизайн, щоб залишатися ремонтом.

Як альтернативне рішення, я пропоную визначити макроси за допомогою лямбда для спрощення ініціалізації, щоб виглядати майже як 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 _={}; /* 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" ));

однак ви можете визначити макрос АДРЕС за допомогою загального макроса INIT легко

#define ADDRESS(x) INIT(address,x)


0

Натхненний цією справді акуратною відповіддю: ( https://stackoverflow.com/a/49572324/4808079 )

Ви можете робити закриття з ламби:

// 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-",
  );
}

У цьому є деякі недоліки, які стосуються переважно неініціалізованих членів. З тих, що кажуть відповіді на коментарі, вона збирається ефективно, хоча я її не перевіряв.

В цілому, я просто думаю, що це акуратний підхід.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.