Инициализация структуры 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
Да, я думал об этом, но у меня есть массив большой структуры. Мне было бы легко и удобно читать таким образом. У вас есть какой-нибудь стиль / хорошая практика инициализации с помощью конструктора, которая также дает лучшую читаемость.
Динеш пиар
2
Рассматривали ли вы библиотеку параметров boost в сочетании с конструкторами? boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Тони Делрой
18
Это не относится к программированию: этот адрес работает нормально только в США. Во Франции у нас нет «провинции», в других частях света нет почтового индекса, бабушка подруги живет в такой маленькой деревне, что ее адрес «Ms X, почтовый индекс». название маленькой деревни "(да, улицы нет). Поэтому тщательно обдумайте, какой действительный адрес для рынка, к которому вы будете применять это;)
Матье М.
5
@MatthieuM. В США нет провинций (это может быть канадский формат?), Но есть штаты, территории и даже крошечные деревни, которые не удосуживаются назвать улицы. Так что проблема соответствия адресов применима даже здесь.
Тим

Ответы:

167

Если вы хотите прояснить, что представляет собой каждое значение инициализатора, просто разбейте его на несколько строк с комментарием к каждой:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};
Wyzard
источник
7
Мне лично нравится и рекомендую этот стиль
Dinesh PR
30
В чем разница между этим и фактическим использованием точечной нотации для более ТОЧНОГО доступа к самому полю, не то чтобы вы экономили место, если это то, что вас беспокоит. Я действительно не понимаю программистов на C ++, когда речь идет о согласованности и написании поддерживаемого кода, они, кажется, всегда хотят сделать что-то другое, чтобы их код выделялся, код должен отражать решаемую проблему, которой не должно быть. сама по себе идиома, нацеленная на надежность и простоту обслуживания.
5
@ user1043000 Ну, например, в этом случае порядок, в котором вы размещаете своих членов, имеет первостепенное значение. Если вы добавите поле в середине вашей структуры, вам придется вернуться к этому коду и найти точное место для вставки новой инициализации, что сложно и скучно. С точечной нотацией вы можете просто поместить свою новую инициализацию в конец списка, не заботясь о порядке. И точечная нотация намного безопаснее, если вам случится добавить тот же тип (например char*), что и один из других членов выше или ниже в структуре, потому что нет риска поменять их местами.
Gui13
6
комментарий orip. Если определение структуры данных изменяется, и никто не думает искать инициализации, или не может найти их все, или делает ошибку, редактируя их, вещи рушатся.
Эдвард Фальк
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";

Это, безусловно, ближе всего к тому, что вы хотели изначально (обнулите все поля, кроме тех, которые вы хотите инициализировать).

Gui13
источник
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и теперь не инициализируйте своего нового участника.
mystery_doctor
25

Как уже упоминали другие, это обозначается как инициализатор.

Эта функция является частью C ++ 20

Самер Чаудхари
источник
17

Идентификаторы полей действительно являются синтаксисом инициализатора Си. В C ++ просто дайте значения в правильном порядке без имен полей. К сожалению, это означает, что вам нужно дать их все (на самом деле вы можете не указывать конечные поля с нулевым значением, и результат будет таким же):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 
Ген
источник
1
Да, вы всегда можете использовать выравнивание структуры.
техасбрус
3
Да, в настоящее время я использую только этот метод (Aligned Struct Initialization). Но я чувствую, что читаемость не очень хорошая. Поскольку моя Структура велика, инициализатор имеет так много данных, и мне трудно отследить, какое значение назначено какому элементу.
Динеш пиар
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] Назначенные инициализаторы; использовать конструкторы

Грамматика 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 }
    { }

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

Гильерме Феррейра
источник
9

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

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
nikc
источник
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);

Отказ от ответственности: я не рекомендую этот стиль

user396672
источник
Это по-прежнему опасно, поскольку позволяет добавлять элемент, addressа код все равно будет компилироваться с миллионами мест, только инициализируя исходные пять элементов. Лучшая часть инициализации структуры состоит в том, что вы можете иметь всех членов, constи это заставит вас инициализировать их всех
mystery_doctor
7

Я знаю, что этот вопрос довольно старый, но я нашел другой способ инициализации, используя 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. Единственный недостаток - плохая ремонтопригодность: каждый раз, когда другой элемент должен быть инициализирован с помощью этого метода, все методы инициализации элемента должны быть изменены.

Fabian
источник
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);
}
run_the_race
источник
Я скомпилировал вышеупомянутую программу с помощью компилятора MinGW C ++ и компилятора Arduino AVR C ++, и оба работали, как и ожидалось. Обратите внимание на #include <cstdio>
run_the_race
4
@run_the_race, это то, о чем говорит стандарт c ++, а не поведение данного компилятора. Однако эта функция появилась в C ++ 20.
Джон
это работает только если структура POD. Так что он прекратит компиляцию, если вы добавите в него конструктор.
oromoiluig
5

Это не реализовано в C ++. (также char*строки? Я надеюсь, что нет).

Обычно, если у вас так много параметров, это довольно серьезный запах кода. Но вместо этого, почему бы просто не инициализировать значение структуры и затем назначить каждому члену?

щенок
источник
6
"(также char*строки? Я надеюсь, что нет)." - Ну, это пример C.
Эд С.
мы можем использовать char * в C ++? В настоящее время я использую его, и он работает (может быть, я делаю что-то не так). Я предполагаю, что компилятор будет создавать постоянные строки «Гамильтон» и «Онтарио» и назначать их адрес членам структуры. Будет ли правильным использовать вместо этого const char *?
Динеш пиар
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;

Не так элегантно, как стиль С, хотя ...

Для локальной переменной требуется дополнительный набор mem (this, 0, sizeof (* this)) в начале конструктора, так что это явно не хуже, и ответ @ gui13 более уместен.

(Обратите внимание, что «temp_address» является переменной типа «temp_address», однако этот новый тип наследуется от «адреса» и может использоваться в любом месте, где ожидается «адрес», так что все в порядке.)

VincentP
источник
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);
}
Unacoder
источник
1

Это возможно, но только если инициализируемая вами структура является структурой POD (обычные старые данные). Он не может содержать никаких методов, конструкторов или даже значений по умолчанию.

кто знает
источник
1

В 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)
Ян Турош
источник
1

В GNUC ++ (кажется, устарел с 2.5, давным-давно :) Смотрите ответы здесь: инициализация C struct с использованием меток. Это работает, но как? ), можно инициализировать структуру следующим образом:

struct inventory_item {
    int bananas;
    int apples;
    int pineapples;
};

inventory_item first_item = {
    bananas: 2,
    apples: 49,
    pineapples: 4
};
Наука Мальчик
источник
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-",
  );
}

С этим связаны некоторые недостатки, в основном связанные с неинициализированными членами. Судя по комментариям, содержащимся в связанных ответах, он эффективно компилируется, хотя я его не проверял.

В целом, я просто думаю, что это аккуратный подход.

Сеф Рид
источник