Не так давно я зацепился глазом за “фигурно-скобоную” инициализацию структуры в одной из тем. Была создана структура
struct commandLine {
uint16_t timeFromStart;
uint8_t deviceName;
boolean onDevice;
uint16_t timeDeviceOn;
};
которая затем инициировалась (в составе массива вот так)
commandLine Formovka[] = {
(commandLine){ 17, 2, 1, 170 },
(commandLine){ 8, 3, 1, 88 },
(commandLine){ 5, 2, 1, 55 },
(commandLine){ 10, 3, 1, 100 },
(commandLine){ 21, 2, 1, 210 },
(commandLine) { 13, 3, 1, 130 }
};
Я тогда ругнулся, что мол это “говнокод” и предложил её заменить не чем иным, как другим говнокодом. Чуть получше, но тоже …
commandLine formovka[] = {
{ .timeFromStart = 17, .deviceName = 2, .onDevice = true, .timeDeviceOn = 170, .name = "Dedka" },
{ .timeFromStart = 8, .deviceName = 3, .onDevice = true, .timeDeviceOn = 88, .name = "Babka" },
{ .timeFromStart = 5, .deviceName = 2, .onDevice = true, .timeDeviceOn = 55, .name = "Vnuchka" },
{ .timeFromStart = 10, .deviceName = 3, .onDevice = true, .timeDeviceOn = 100, .name = "Zhuchka" },
{ .timeFromStart = 21, .deviceName = 2, .onDevice = true, .timeDeviceOn = 210, .name = "Koshka" },
{ .timeFromStart = 13, .deviceName = 3, .onDevice = true, .timeDeviceOn = 130, .name = "Myshka" }
};
И с тех пор у меня всё свербело объяснить почему это говнокод, какие здесь грабли и как надо сделать, чтобы эффект граблей если уж не убрать совсем, то минимизировать.
Ну, вот давайте начнём. Начнём в первого пример (до “моего” примера мы ещё доберёмся и ему тоже достанется.
Вариант с безымянной фигурно-скобочной инициализацией
Итак, первый пример, что с ним не так?
Для начала заметим, что синтаксически такую инициализацию можно выразить по разному. Можно, например, вообще убрать преобразование к типу или поставить не не такое (имя типа в скобках) в т.н. функциональной нотации. В примере ниже используются все три варианта (строки №№ 28-30) и всё, как видите, работает на ура.
Пример фигурно-скобочно инициализации в трёх вариантах
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox {
int width; // ширина
int length; // длина
int height; // высота
int weight; // вес
void print(Print& p) const {
p.print("{ width: ");
p.print(width);
p.print("; length: ");
p.print(length);
p.print("; height: ");
p.print(height);
p.print("; weight: ");
p.print(weight);
p.println(" }");
}
};
//
// Массив структур, каждый элемент инициализируем чуть по-разному
//
SBox payLoad[] {
{ 40, 200, 32, 43 },
(SBox) { 38, 120, 40, 26 },
SBox({ 60, 150, 48, 51 })
};
// Количество элементов в массиве
static constexpr uint8_t totalBoxes = sizeof(payLoad) / sizeof(payLoad[0]);
//
// Просто печатаем массив, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
for(uint8_t i = 0; i < totalBoxes; ++i) {
Serial.print("payLoad[");
Serial.print(i);
Serial.print("]=");
payLoad[i].print(Serial);
}
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Результат работы примера
payLoad[0]={ width: 40; length: 200; height: 32; weight: 43 }
payLoad[1]={ width: 38; length: 120; height: 40; weight: 26 }
payLoad[2]={ width: 60; length: 150; height: 48; weight: 51 }
Чтобы закрыть эту тему и больше не к ней не возвращаться, скажу, что никакой разницы между этими тремя синтаксисами инициализации нет, они идентичны. Первый (в строке №28) чуть хуже остальных тем, что там не указан тип. Компилятор-то разберётся, а вот для читателя-человека тип лучше указывать. Но функционально они идентично и впредь я не буду их разделять, а буду говорить о граблях фигурно-скобочной инициализации вообще – это всех трёх синтаксисов касается.
Хотя я и считаю такую инициализацию говнокодом, сам я ею пользуюсь (грешен человек). Ну, а поскольку пользуюсь, то время от времени налетаю на те самые грабли. Например, вот это наступление на них, родимых, стоило мне доброго часа траха, чтобы понять что не так с программой, которая работает у меня уже много лет.
Грабли №1
Выделим грабли №1: структура, даже системная, всеми и много используемая и очень старая, может иметь разный порядок следования членов. И если типы у членов одинаковы (или совместимые), то компилятор ничего не заметит, просто присвоит значения не тем членам, каким нужно и всё пойдёт наперекосяк. Если типы одинаковые, то бороться с этим нельзя никак (компилятору-то всё равно какому полю структуры что присваивать. Если это не системная структура (как было у меня по ссылке), а Ваша, то хорошо бы, когда Вы её меняете, заодно менять и все инициализации. Но, положа руку на сердце, Вы точно сможете вспомнить все такие места в проекте из 150 файлов, который Вы разрабатываете уже год? Вот и я так же
Попробуйте сами в моём примере выше поменять местами какие-нибудь элементы структуры, а инициализацию не трогайте и убедитесь, что компиляция проскочит “молча”, а числа будут присвоены не тем элементам
Грабли №2
На этот раз, мы просто добавим новый элемент в структуру. Для начала в самый конец (строка №10):
Пример с добавлением нового члена структуры в конец
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox {
int width; // ширина
int length; // длина
int height; // высота
int weight; // вес
int color;
void print(Print& p) const {
p.print("{ width: ");
p.print(width);
p.print("; length: ");
p.print(length);
p.print("; height: ");
p.print(height);
p.print("; weight: ");
p.print(weight);
p.println(" }");
}
};
//
// Массив структур, каждый элемент инициализируем чуть по-разному
//
SBox payLoad[] {
{ 40, 200, 32, 43 },
(SBox) { 38, 120, 40, 26 },
SBox({ 60, 150, 48, 51 })
};
// Количество элементов в массиве
static constexpr uint8_t totalBoxes = sizeof(payLoad) / sizeof(payLoad[0]);
//
// Просто печатаем массив, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
for(uint8_t i = 0; i < totalBoxes; ++i) {
Serial.print("payLoad[");
Serial.print(i);
Serial.print("]=");
payLoad[i].print(Serial);
}
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Сообщения компилятора
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:30:27: warning: missing initializer for member 'SBox::color' [-Wmissing-field-initializers]
(SBox) { 38, 120, 40, 26 },
^
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:31:26: warning: missing initializer for member 'SBox::color' [-Wmissing-field-initializers]
SBox({ 60, 150, 48, 51 })
^
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:32:1: warning: missing initializer for member 'SBox::color' [-Wmissing-field-initializers]
};
^
Скетч использует 2172 байт (6%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 276 байт (13%) динамической памяти, оставляя 1772 байт для локальных переменных. Максимум: 2048 байт.
Результат работы примера
payLoad[0]={ width: 40; length: 200; height: 32; weight: 43 }
payLoad[1]={ width: 38; length: 120; height: 40; weight: 26 }
payLoad[2]={ width: 60; length: 150; height: 48; weight: 51 }
Ну, по крайней мере, нас предупредили, что что-то пошло не так (во всех трёх синтаксисах, как я и обещал). Это лучше, чем ничего, но предупреждение легко не заметить (особенно, если Вы используете библиотеки типа iarduinoXXX, в которых этих предупреждений целые страницы и новые в них просто затеряются). Конечно, лучше бы компилятор выдал ошибку и отказался бы компилировать - этого мы бы не смогли не заметить и исправили бы.
Отработало, кстати, всё штатно - все числа на месте. Другое дело, если мы вставим новый член не в конец, а в начало или в середину. Давайте попробуем (строка №6):
Пример с добавлением нового члена структуры в начало
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox {
int color;
int width; // ширина
int length; // длина
int height; // высота
int weight; // вес
void print(Print& p) const {
p.print("{ width: ");
p.print(width);
p.print("; length: ");
p.print(length);
p.print("; height: ");
p.print(height);
p.print("; weight: ");
p.print(weight);
p.println(" }");
}
};
//
// Массив структур, каждый элемент инициализируем чуть по-разному
//
SBox payLoad[] {
{ 40, 200, 32, 43 },
(SBox) { 38, 120, 40, 26 },
SBox({ 60, 150, 48, 51 })
};
// Количество элементов в массиве
static constexpr uint8_t totalBoxes = sizeof(payLoad) / sizeof(payLoad[0]);
//
// Просто печатаем массив, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
for(uint8_t i = 0; i < totalBoxes; ++i) {
Serial.print("payLoad[");
Serial.print(i);
Serial.print("]=");
payLoad[i].print(Serial);
}
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Сообщения компилятора
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:30:27: warning: missing initializer for member 'SBox::weight' [-Wmissing-field-initializers]
(SBox) { 38, 120, 40, 26 },
^
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:31:26: warning: missing initializer for member 'SBox::weight' [-Wmissing-field-initializers]
SBox({ 60, 150, 48, 51 })
^
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:32:1: warning: missing initializer for member 'SBox::weight' [-Wmissing-field-initializers]
};
^
Скетч использует 2172 байт (6%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 276 байт (13%) динамической памяти, оставляя 1772 байт для локальных переменных. Максимум: 2048 байт.
Результат работы примера
payLoad[0]={ width: 200; length: 32; height: 43; weight: 0 }
payLoad[1]={ width: 120; length: 40; height: 26; weight: 0 }
payLoad[2]={ width: 150; length: 48; height: 51; weight: 0 }
Как видите, здесь всё гораздо хуже. Компилятор предупредил (как и раньше) и программа неправильно инициализировала структуру (первое число ушло в инициализацию поля color
, и всё сдвинулось). Беда!
Грабли №3
Это особый вид граблей, которые подстерегаю наиболее “продвинутых парней” (не по Пелевину, а в “хорошем смысле”).
Итак, давайте вообразим себя продвинутыми и скажем, а нафига нам в нашей структуре такой лоховской метод print, который требует от нас в писать в коде уродливую конструкцию типа payLoad[i].print(Serial);
(строка №46 в последнем примере). Давайте сделаем нашу структуру дружественной к потоковому выводу, чтобы мы могли писать как нормальные люди: Serial.println(payLoad[i]);
. ООП тут нас или где? Так давайте и писать по ООП’ному!
Как это делается я уже объяснял, повторяться не буду. Сделаем это:
и тут же налетим на грабли! Структуру, у которой есть нетривиальный конструктор нельзя инициализировать фигурно-скобоxной инициализацией
Пример с потоковой печатью
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox : public Printable {
int color;
int width; // ширина
int length; // длина
int height; // высота
int weight; // вес
size_t printTo(Print& p) const {
size_t res = p.print("{ width: ");
res += p.print(width);
res += p.print("; length: ");
res += p.print(length);
res += p.print("; height: ");
res += p.print(height);
res += p.print("; weight: ");
res += p.print(weight);
res += p.println(" }");
return res;
}
};
//
// Массив структур, каждый элемент инициализируем чуть по-разному
//
SBox payLoad[] {
{ 40, 200, 32, 43 },
(SBox) { 38, 120, 40, 26 },
SBox({ 60, 150, 48, 51 })
};
// Количество элементов в массиве
static constexpr uint8_t totalBoxes = sizeof(payLoad) / sizeof(payLoad[0]);
//
// Просто печатаем массив, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
for(uint8_t i = 0; i < totalBoxes; ++i) {
Serial.print("payLoad[");
Serial.print(i);
Serial.print("]=");
Serial.println(payLoad[i]);
}
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Сообщения компилятора
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:31:27: error: no matching function for call to 'SBox::SBox(<brace-enclosed initializer list>)'
(SBox) { 38, 120, 40, 26 },
^
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate: SBox::SBox()
struct SBox : public Printable {
^~~~
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate expects 0 arguments, 4 provided
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate: constexpr SBox::SBox(const SBox&)
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate expects 1 argument, 4 provided
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate: constexpr SBox::SBox(SBox&&)
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate expects 1 argument, 4 provided
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:32:26: error: no matching function for call to 'SBox::SBox(<brace-enclosed initializer list>)'
SBox({ 60, 150, 48, 51 })
^
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate: SBox::SBox()
struct SBox : public Printable {
^~~~
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate expects 0 arguments, 1 provided
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate: constexpr SBox::SBox(const SBox&)
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: no known conversion for argument 1 from '<brace-enclosed initializer list>' to 'const SBox&'
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: candidate: constexpr SBox::SBox(SBox&&)
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:5:8: note: no known conversion for argument 1 from '<brace-enclosed initializer list>' to 'SBox&&'
exit status 1
Compilation error: no matching function for call to 'SBox::SBox(<brace-enclosed initializer list>)'
Опять же, заметьте, ругается на все три синтаксиса – ну, нет между ними разницы.
Ну, хоть ошибку выдал, а не предупреждение и “неправильную работу” как было раньше. И на том спасибо
Вариант с инициализаций по именам полей
Теперь давайте перейдём к моему варианту (инициализация по именам полей) и посмотрим какие грабли ушли, а какие новые появились. Итак, инициализируем по именам. Тут синтаксис один, поэтому выбрасываем массив, делаем примеры на одиночной структуре - код короче будет.
Грабли всё те же, поэтому я не буду их подробно описывать, просто буду ссылаться на них по номерам. Подробно опишу только новые.
Изначальный код. Возвращаем наш “лоховской” print и делаем инициализацию по именам полей.
Пример с инициализацией по именам полей
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox {
int width; // ширина
int length; // длина
int height; // высота
int weight; // вес
void print(Print& p) const {
p.print("{ width: ");
p.print(width);
p.print("; length: ");
p.print(length);
p.print("; height: ");
p.print(height);
p.print("; weight: ");
p.print(weight);
p.println(" }");
}
};
//
// Структура проинициализированная по именам
//
SBox payLoad = {
.width = 40,
.length = 200,
.height = 32,
.weight = 43
};
//
// Просто печатаем структуру, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
payLoad.print(Serial);
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Результат работы примера
{ width: 40; length: 200; height: 32; weight: 43 }
Вроде всё нормально.
Грабли №1
Меняем местами элементы и пробуем откомпилировать
Пример со сменой мест элементов
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox {
int length; // длина
int height; // высота
int width; // ширина
int weight; // вес
void print(Print& p) const {
p.print("{ width: ");
p.print(width);
p.print("; length: ");
p.print(length);
p.print("; height: ");
p.print(height);
p.print("; weight: ");
p.print(weight);
p.println(" }");
}
};
//
// Массив структур, каждый элемент инициализируем чуть по-разному
//
SBox payLoad = {
.width = 40,
.length = 200,
.height = 32,
.weight = 43
};
//
// Просто печатаем структуру, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
payLoad.print(Serial);
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Сообщения компилятора
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:32:1: sorry, unimplemented: non-trivial designated initializers not supported
};
^
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:32:1: sorry, unimplemented: non-trivial designated initializers not supported
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:32:1: sorry, unimplemented: non-trivial designated initializers not supported
exit status 1
Compilation error: exit status 1```
А вот хренушки! Не работает! Зато компилятор выдал честную ошибку, а не какое-то там предупреждение. Это совсем неплохо! Это заставит нас поменять всё и везде и не пропустит ошибку в код! Считаем, что эти грабли нашему нынешнему подходу не опасны!
Грабли №2
Добавление нового элемента в структуру приведёт опять же к честной ошибке компиляции (попробуйте проделать это самостоятельно). Ну, а раз это ошибка, а не предупреждение, считаем, что и здесь всё значительно лучше, чем было при “безымянном подходе”.
Грабли №3
Ну, что, опять вставляем потоковую печать и любуемся:
Пример с потоковой печатью и инициализацией по именам полей
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox : public Printable {
int width; // ширина
int length; // длина
int height; // высота
int weight; // вес
size_t printTo(Print& p) const {
size_t res = p.print("{ width: ");
res += p.print(width);
res += p.print("; length: ");
res += p.print(length);
res += p.print("; height: ");
res += p.print(height);
res += p.print("; weight: ");
res += p.print(weight);
res += p.println(" }");
return res;
}
};
//
// Структура проинициализированная по именам
//
SBox payLoad = {
.width = 40,
.length = 200,
.height = 32,
.weight = 43
};
//
// Просто печатаем структуру, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
Serial.println(payLoad);
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Сообщения компилятора
D:\GoogleD\Soft\Uroki\StructInit\SturctInit2\SturctInit2.ino:33:1: error: could not convert '{40, 200, 32, 43}' from '<brace-enclosed initializer list>' to 'SBox'
};
^
exit status 1
Compilation error: could not convert '{40, 200, 32, 43}' from '<brace-enclosed initializer list>' to 'SBox'```
и опять кушаем ошибку компиляции.
Ну, что, пока подход “по именам” выглядит гораздо лучше “безымянного”. По крайней мере, он ни разу не скомпилировал неправильно работающий код из-за нашей забывчивости. Т.е. всякий раз, когда мы меняем структуру и забываем поправить инициализацию, компилятор ругнётся и заставит на это сделать! Т.е. в целом, поход годный. Но, структуры с нетривиальными конструкторами (грабли №3) так инициализировать, по-прежнему, нельзя (а в наше время это неприлично), а, кроме того здесь нас подстерегают
Грабли №4
Такая инициализация не описана в стандарте языка – это расширение GCC. А это значит, что перейдя на другой компилятор, мы можем столкнуться с тем, что там это не работает
Как это можно делать “в наше время”?
Главная идея: добавить в структуру конструктор. А если мы не планируем её инициализировать ничем, кроме известных во время компиляции констант (как во всех наши примерах), так лучше не просто конструктор, а constexpr
конструктор!
Возвращаем нашу “продвинутую” потоковую печать, добавляем конструктор и получаем изначальный код этого раздела:
Пример с потоковой печатью и конструктором
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox : public Printable {
int length; // длина
int height; // высота
int width; // ширина
int weight; // вес
constexpr SBox(const int l, const int h, const int wi, const int we) : length(l), height(h), width(wi), weight(we) {}
size_t printTo(Print& p) const {
size_t res = p.print("{ width: ");
res += p.print(width);
res += p.print("; length: ");
res += p.print(length);
res += p.print("; height: ");
res += p.print(height);
res += p.print("; weight: ");
res += p.print(weight);
res += p.println(" }");
return res;
}
};
//
// Массив структур, каждый элемент инициализируем чуть по-разному
//
SBox payLoad(40, 200, 32, 43);
//
// Кстати, если нужен массив, то с конструктором он инициализируется вот так:
//
SBox payLoadArray[] = {
SBox(40, 200, 32, 43),
SBox(34, 134, 16, 50),
SBox(32, 321, 24, 40)
};
//
// Просто печатаем структуру, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
Serial.println(payLoad);
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Результат работы примера
{ width: 32; length: 40; height: 200; weight: 43 }
Всё работает с потоковой печатью! Значит, грабли №3 здесь не действуют!
Проверим остальные грабли.
Грабли №1
Здесь всё интереснее, давайте смотреть внимательно:
Изменяем порядок следования полей структуры
//
// Простая структура с методом печати в
// поток (например, Serial, SoftSerial, LCD и т.п.)
//
struct SBox : public Printable {
int height; // высота
int width; // ширина
int length; // длина
int weight; // вес
constexpr SBox(const int l, const int h, const int wi, const int we) : length(l), height(h), width(wi), weight(we) {}
size_t printTo(Print& p) const {
size_t res = p.print("{ width: ");
res += p.print(width);
res += p.print("; length: ");
res += p.print(length);
res += p.print("; height: ");
res += p.print(height);
res += p.print("; weight: ");
res += p.print(weight);
res += p.println(" }");
return res;
}
};
//
// Массив структур, каждый элемент инициализируем чуть по-разному
//
SBox payLoad(40, 200, 32, 43);
//
// Кстати, если нужен массив, то с конструктором он инициализируется вот так:
//
SBox payLoadArray[] = {
SBox(40, 200, 32, 43),
SBox(34, 134, 16, 50),
SBox(32, 321, 24, 40)
};
//
// Просто печатаем структуру, чтобы посмотреть,
// как отработала инициализация
//
void setup() {
Serial.begin(9600);
Serial.println(payLoad);
}
// Здесь ничего не делаем
void loop() { delay(100500); }
Сообщения компилятора
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino: In constructor 'constexpr SBox::SBox(int, int, int, int)':
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:8:6: warning: 'SBox::length' will be initialized after [-Wreorder]
int length; // длина
^~~~~~
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:6:6: warning: 'int SBox::height' [-Wreorder]
int height; // высота
^~~~~~
D:\GoogleD\Soft\Uroki\StructInit\SturctInit1\SturctInit1.ino:11:12: warning: when initialized here [-Wreorder]
constexpr SBox(const int l, const int h, const int wi, const int we) : length(l), height(h), width(wi), weight(we) {}
^~~~
Скетч использует 2232 байт (6%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 250 байт (12%) динамической памяти, оставляя 1798 байт для локальных переменных. Максимум: 2048 байт.
Результат работы примера
{ width: 32; length: 40; height: 200; weight: 43 }
Итак, что произошло. Компилятор нас предупредил о том, что он будет инициализировать поля не в том порядке, как написано у нас в строке №11, а в том, в котором эти элементы объявлены в строках №№6-9.
Но наша программа отработала правильно (сравните результат работы с предыдущим примером!). Т.е. все поля получили те значения, которые мы и хотели присвоить!
Важно ли нам в каком порядке инициализируются поля (о чём нас предупредил компилятор)? В нашем пример – нет. Это было бы важно, если бы следующее поле инициализировалось бы с использованием значения предыдущего. Но, разумеется, лучше их там писать в правильном порядке.
Итог - программа отработала правильно. Считаем, что эти грабли неопасны!
Грабли №2
Добавление полей. Попробуйте сами взять изначальный код из данного раздела и добавить поле (куда угодно). Вас просто обругает компилятор и заставят его тоже проинициализировать в строке №11. Если пока не знаете чем – проинициализируйте нулём. Только порядок следования соблюдайте (см. обсуждение граблей №1 в этом разделе).
Ну, грабли №3 при таком походе отсутствуют (мы это уже заметили), грабли №4 - тоже, здесь всё стандартно.
Таким образом,
использование конструктора позволяет избежать всех граблей, какие мы здесь рассматривали. При этом, такой конструктор не стоит ничего в плане кода и/или памяти. Он отрабатывает во время компиляции и в код идёт просто проинициализированный кусок памяти - т.е. он нас требуется только написать единственную несложную строку (№11 в нашем примере), это совершенно бесплатно и это избавляет нас от граблей.
Ну, как-то так. Спасибо тем, кто “асилил”.
Дополнение от 11.09.2024:
простите дурака, совсем вылетело из головы, что в ISO/IEC 14882:2020 т.н. “назначенная инициализация” (с указанием имён полей) получила законную прописку. Так что о граблях №4 можно потихоньку забывать. Пока ещё не все реализации поддерживают стандарт 2020, но движение идёт в правильном направлении.