LINUX.ORG.RU

Инициализация полей класса

 ,


0

1

Читаю книгу Beautiful C++. Там рассматривается вопрос о правильной инициализации полей класса и в качестве начального примера используется следующий код:

class piano
{
public:
  piano();
private:
  int number_of_keys;
  bool mechanical;
  std::string manufacturer;
};

piano::piano()
{
  number_of_keys = 88;
  mechanical = true;
  manufacturer = "Yamaha";
}

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

piano::piano()
  : number_of_keys(88)
  , mechanical(true)
  , manufacturer("Yamaha")
{}

Мне более привычен немного другой стиль написания:

piano::piano() :
    number_of_keys(88),
    mechanical(true),
    manufacturer("Yamaha")
{}

А вот ещё нашёл документ, описывающий гугловский стиль написания кода на C++ https://google.github.io/styleguide/cppguide.html#Constructor_Initializer_Lists и в нём этот код выглядел бы вот так:

piano::piano()
    : number_of_keys(88),
      mechanical(true),
      manufacturer("Yamaha") {
}

Какой стиль инициализации в конструкторе вы предпочитаете?

P.S. на этом разбор инициализации в книге не заканчивается, но меня заинтересовал стиль написания кода.



Последнее исправление: zg (всего исправлений: 1)
Ответ на: комментарий от soomrack

Боль скорее вытекает из-за беззнаковости размеров в std, я данный подход не разделяю (не лишь я, а в тех же core guidelines тоже), беззнаковым место в масках всяких, флагах. В итоге простейший случай «Type t{vec.size()}» вызывает боль, либо костылить static_cast, либо брать круглые скобки.

Т.е. начинаются постоянные отступления от правила, которое вы озвучили (я в одно время тоже пытался ему следовать). Ну и нет последовательности - int = unsigned - ок, int (uint) - ok; int {uint} = warning. В общем подумал я однажды и вовсе отказался по максимуму от {}, лишь головная боль от них. Зря эту дрянь про narrowing conversion впихнули в стандарт, могло бы быть красиво.

kvpfs_2
()
Ответ на: комментарий от kvpfs_2

Ну в целом да, есть проблемы, но для многих вещей есть принципиальная разница в {} и (), как пример с

std::vector(10);
std::vector{10};

или

std::string(40, '*');  // ***...40 раз...***
std::string{40, '*'};  // (*

и вроде как сейчас хотят вводить знаковый size…

PS: narrowing conversion warning можно, наверное, и отключить…

soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 1)
Ответ на: комментарий от kvpfs_2

беззнаковым место в масках всяких, флагах.

И в тех случаях, где может быть переполнение, которые фиксируется постфактум, или которое не нужно фиксировать вовсе.

soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 1)
Ответ на: комментарий от soomrack

narrowing conversion warning можно, наверное, и отключить…

Так в случае фигурной инициализации это не warning, а ошибка компиляции. Это становится warning если вернуться на инициализацию круглыми скобками.

zg
() автор топика
Ответ на: комментарий от soomrack

И в тех случаях, где может быть переполнение, которые фиксируется постфактум, или которое не нужно фиксировать вовсе.

Ну всё это спец кейсы, на которые смотрят под лупой. Гораздо хуже пропустить вот такой микс: «a.get_uint() - b.get_int() + get_uint()». Не место беззнаковым в рутинных, тривиальных кейсах.

В общем идея может и хорошей была, но реальность была с оврагами. Например, вот что в ГЦЦ говорили перестав ронять компиляцию с ошибкой про новую фичу:

The standard only requires that "a conforming implementation shall issue at least one diagnostic message" so compiling the program with a warning is allowed.  As Andrew said, -Werror=narrowing allows you to make it an error if you want.

G++ 4.6 gave an error but it was changed to a warning intentionally for 4.7 because many people (myself included) found that narrowing conversions where one of the most commonly encountered problems when trying to compile large C++03 codebases as C++11.  Previously well-formed code such as char c[] = { i, 0 }; (where i will only ever be within the range of char) caused errors and had to be changed to char c[] = { (char)i, 0 }
kvpfs_2
()
Последнее исправление: kvpfs_2 (всего исправлений: 2)
Ответ на: комментарий от kvpfs_2

Если есть вычитание в правой части, то беззнаковый тип в левой это очень подозрительно выглядит.

Как я понимаю, проблемы в основном от старых проектов, где меньше внимания уделялось тому, как литералы приводятся к типу. В новых проектах, кмк, лучше писать инициализацию так, чтобы было прямо указано, что за тип, без неявного приведения… Но опять же, все от проекта и от ситуации зависит.

В целом, мне идея {} нравится, и я ее по возможности использую.

soomrack ★★★★★
()
Ответ на: комментарий от soomrack

Идея - супер, правильная. Но эта тухлая диагностика … . Ну зачем её было в стандарт, оно тянет на опциональную диагностику компилятора. А так получается, взяв на вооружение всю эту концепцию с narrow conv, нужно писать такую лабуду для консистентности:

void f(int i) {}

int main() {
    double d = 4.3;
    f({d}); // отлично, получаем предупреждение, без {} - не получили бы
}

Я так с ума сходить не готов - паковать аргументы функций в скобки для следования намотанонй на ус концепции.

В общем подожду, пока разум возобладает и данная диагностика отправится к garbage collector support.

kvpfs_2
()
Ответ на: комментарий от kvpfs_2

В этом примере, кмк, предупреждение правильное. Типы данных разные, есть неявное приведение типов. Но это просто мое желание иметь более строгую типизацию в С++, чем та, которая есть, чтобы если что-то неявно происходит, что потенциально может иметь последствия, то выкидывался бы ворнинг (например, если неявно double в int, то ворнинг, а если неявно short int в int, то все норм).

Поэтому в данном случае я бы писал f(d) или прямо бы указал приведение к типу так, как хочу: f(static_cast<int>(d)).

soomrack ★★★★★
()
Ответ на: комментарий от soomrack

Visual Studio 2022 17.10.3

Вот здесь https://www.youtube.com/watch?v=Dg77_dp8jgE чувак пишет в Qt Creator тоже под виндой, судя по логам в MingGW и на 4:44 он рассказывает и наглядно показывает то, что фигурная инициализация запрещает сужающие преобразования.

У меня в MSYS2 g++ 14.1.0 по умолчанию выдаёт лишь предупреждение, если только не добавить опцию -pedantic-errors. Возможно Qt Creator включает эту опцию по умолчанию.

zg
() автор топика
Ответ на: комментарий от soomrack

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

Так ведь в том и дело, что f(d) с неявным кастом дабл в инт не даст никакого воринга, тебе нужно писать вот этот жуткий костыль f({d}), чтобы следовать данной концепции, которую ты принял. Иначе у тебя огромная брешь, это как быть ярым сторонником ЗОЖ, но при этом по выходным в хлам упарываться героином. Ты ведь не станешь паковать все аргументы функций в {}? Ведь да? В общем поздно такие штуки в стандарт тащить, применительная практика сложилась, теперь таким вещам место только в качестве опциональных флагов компилятору, где параноики могут получать ошибку даже в случае f(d).

f(static_cast(d))

Опять это все такие жуткие костыли. Без обсуждения того, что голый static_cast для таких целей - нельзя, ибо мест таких будет много, значит будет потерян смысл конструкций из семейства .*_cast<>(), когда можно грепнуть код и посмотреть на все эти места под лупой, значит надо делать обертки make_int/signed/unsigned/…, в общем геморрой на ровном месте. Так ещё и шаблонный код существует, и конкретные типы/размерности могут быть неизвестны, а значит с ненулевой вероятностью в шаблонном коде будут уместны конструкции вроде:

typename <typename F, typename F_traits>
void action(F f) {
    f(make_int<F_traits::arg_type>(d))
}

В общем не нужна никакая более строгая типизация в плюсах, ибо больно и все привыкли иначе. Доп флаги для параноиков - отлично, но не в стандарт. Лично мое мнение, не переубеждаю

kvpfs_2
()
Ответ на: комментарий от kvpfs_2

Ты ведь не станешь паковать все аргументы функций в {}?

Нет, конечно. Я использую {} только чтобы различать простую инициализацию полей значением со сложной инициализацией, в которой параметры не являются значениями для создаваемого объекта. Для этого, имхо, сейчас всего достаточно.

soomrack ★★★★★
()
Ответ на: комментарий от soomrack

{} только чтобы различать простую инициализацию полей значением со сложной инициализацией, в которой параметры не являются значениями для создаваемого объекта.

Вот на этом и надо было остановиться стандартописателям, кому там моча в голову ударила с проверкой на narrow conversion …, в итоге загадили вполне годную и полузную идею, когда читателю кода это давало бы полезные подсказки

kvpfs_2
()