Pull to refresh

Comments 74

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

К тому же компилятор обычно предупреждает, если пренебречь этим советом.
вот так не ругается
$ c++ 2.cpp

а вот так ругается
$ c++ -Wall 2.cpp
2.cpp: In constructor 'A::A(int)':
2.cpp:26: warning: 'A::b' will be initialized after
2.cpp:25: warning: 'S A::a'
2.cpp:28: warning: when initialized here
Ну мы же правильные программисты, мы же всегда включаем ворнинги.
Ворнинги мало включить, их ещё нужно внимательно читать… побожитесь, что прям всегда читаете все ворнинги ,-)
Ужасная фраза :) Ересь :)

Когда писал на С++ — перед тем как релизить — ни одного ворнинга не должно быть.
Не обращать внимания на ворнинги — это большое зло. Еще большее зло — релизить чтото, компилящееся с ворнингами(ну бесят они. проект с ворнингами — грязный проект :) ).
Это понятно :-)
Но грабли приходится ловить за долго до того как релизить :-)
А на счёт грязных проектов… к сожалению не всегда весь код твой… часто приходится мириться с чужими ворнингами. Очень часто. И часто это связано даже не с небрежностью программистов, а с разными версиями компилляторов, операционок, библиотек… на которых велась разработка… В общем, я не верю, что старый кросплатформенный проект может собираться прям вот везде без ворнингов.
Для меня, обычного виндо-юзера, «старый кросплатформенный проект» — это чтото вроде динозавра :) Большой, страшный и очень редко встречающийся. Сидел бы на линуксе, мож понял бы вас лучше.
Иногда ещё авторы компиляторов «из хороших соображений» делают новые ворнинги там, где раньше обходилось.

Например, последние версии Visual C++ ругаются на printf() и тому подобные вещи. Хотя я, естественно, не буду из-за этого менять старый, многократно испытанный код.
Эти ворнинги отключаются скопом одним махом.
Это ясно, но смысл ворнингов ведь не в том, чтобы их отключать?
Если у вас «многократно испытанный код», который вы не собираетесь изменять из-за нововведенных ворнингов компилятора, то лучше их отключить вовсе, чтобы среди них не затерялись другие.
>Например, последние версии Visual C++ ругаются на printf()

И правильно делают. См. хотя бы «Защищённый код».
Всё не так очевидно — я не спорю с тем, что в новом проекте printf() незачем. Но если код отлично работает, я не хочу, чтобы в нём появлялись новые ворнинги. Мне не нужно ломать отлаженную программу лишь из-за того, что компилятор стал более параноидальным.

См. рекомендацию мне выше — «отключить ворнинги вовсе». Вот так оно и работает — приходится отключать вовсе, и смысл их пропадает…
Вы отключаете только предупреждения новой CRT. Остальные то остаются. Ну а предупреждения новой CRT полезны в новых проектах, когда нет тонны унаследованного кода, который никто не будет переписывать, когда есть возможность написать с нуля и правильно.
Тоже верно, и всё равно неоднозначно. Я это к тому говорю, что хорошего решения толком нет.
Да, допустим, компилятор беспокоит секьюрность. Но что он мне предлагает? printf_s(), scanf_s() и прочее — насколько мне известно, функции не из состава ANSI C++, т.е. он предлагает сломать кроссплатформенность.

А старые добрые функции иногда наиболее просты в использовании, бывает и такое… Я бы не сказал, что использование printf() «неправильно».

Если взять практически любой учебник по С++, там написано, что препроцессор — это неправильно. А теперь открываем буст, и видим, что половина его построена на препроцессоре (Boost.Preprocessor, Boost.Foreach и пр.)
>Вот так оно и работает — приходится отключать вовсе

Ни в коем случае! Тогда уж лучше используй «#pragma warning (suppress: номер)» для однократного подавления предупреждений в одной строчке или «disable» в обрамлении «push» и «pop» (http://msdn.microsoft.com/en-us/library/2c8f766e(loband).aspx).

>Мне не нужно ломать отлаженную программу лишь из-за того, что компилятор стал более параноидальным.

Сам не сломаешь — кто-то другой сломает. Отлаженная — это как? Ты поручишься, что обошёл абсолютно все уязвимости printf'а?
> Ты поручишься, что обошёл абсолютно все уязвимости printf'а?

Абсолютно поручусь. У меня есть код, в котором с помощью sprintf() переводят целое 32-разрядное число в шестнадцатеричную запись. Вот как-то слабо верится, что здесь может случиться переполнение чего бы то ни было :) Все размеры заранее известны.
>Все размеры заранее известны.

А почему тогда не использовать более безопасные аналоги sprintf'а с указанием этих размеров? Компилятор ругаться перестанет.

А то и вовсе можно использовать стандартные строковые стримы, Boost.Format, Boost.LexicalCast.
Насколько мне известно, эти аналоги не кросплатформенны.

Стримы и прочее — верно, активно пользуюсь. Но, положа руку на сердце, нет более простого и эффективного кода перевода числа в шестнадцатеричную строку, чем две строки: объявление буфера и вызов sprintf(). В остальном согласен :)
Чтобы быть уверенным, что не пропустил ни одного ворнинга, нужно включать treat warnings as errors.
Читаю и исправляю обязательно, не раз это меня спасало. «Божиться» не умею.
-Wall -W -Werror!
(hint: -Wall не все включает)
UFO just landed and posted this here
ISO/IEC 14882:1998 12.6.2 [class.base.init] #5 стр 197.
UFO just landed and posted this here
UFO just landed and posted this here
Стандарт стандартом, а убеждать надо как раз таки примерчиками. Я с таким успехом могу посылать всех читать стандарт по любому нюансу С++, вместо того чтобы писать статью.
ИМХО, даже в «исправленном» виде этот код плох, ибо потенциально порождает баги!!!
Ибо код пишет один человек, интегрировать его может другой, а дорабатывать (через пару месяцев или лет) — третий…
Я-бы на код-ревю такой код «зарубил». А на второй-третий раз задумался о замене такого программиста :-(
Вот так это должно выглядеть в нашей «лавочке», согласно внутрифирменного стандарта:

A(int x): b(x), a(х) {}
Ну а если бы было так:
A(int x): b(x+1), a(b) {}
вы всё равно скажете, что лучше
A(int x): b(x+1), a(x+1) {}
ну я бы сказал что да… либо если уж совсем извраты какие нить, то проще вынести в тело конструктора
Уж лучше так, чем баги плодить!!!
А ещё лучше в сложных случаях (а Вы на них намекаете :) инициализацию проводить в теле констрактора или в функции init…
Я полностью согласен с вами, что всегда есть выход лучше. Но чтобы понять, что именно он лучше, хорошо бы знать про возможные грабли, согласитесь ,-)

Кроме того, есть два важных аспекта:
1) Ваши предложения не всегда прокатят
2) Существуют ситуации, когда решение с инициализацией просто само просится. Классика:
A(int x): vectorSize(x+1), vector(vectorSize) {}
Часто, такое решение возникает эволюционно: сперва наблюдается
A(int x): vector(x+1) {}
потом оказывается, что хорошо бы хранить размер и рождется
A(int x): vectorSize(x+1), vector(vectorSize) {}

Так вот. Пост о том, что при этом возможны грабли.
1) когда именно не прокатит перенос инциализации в тело конструктора?
2) второй вариант как раз просит выноса vector(vectorSize) в тело конструктора
1) когда нет конструктора без параметров.
вот этот пример не соберётся

#include class S {
public:
S(int x) {}
};

class A {
private:
S a;
public:
A() { a(1); }
};
int main() {
A a();
return 0;
}

а если заменить

A() { a(1); }

на

A(): a(1) {}

то всё будет хорошо.

2) Опять же, vector можно засунуть в тело, только если есть конструктор vector::vector().
чё-то я… неудачный пример написал :-)) но смысл наверно все уловили, раз вопросов нет ,-)
да я чот забыл уж про этот топик;)
я бы в таких случаях скорее сделал бы скорее:
class A {
private:
S *a;
public:
A() { a = new S(1); }
};
~A() {delete a;}
int main() {
A a();
return 0;
}
Это совсем другой класс!
Из-за ссылок для него нужно иначе писать конструкторы копирования и операции присваивания… он даже при линковке ведёт себя иначе!
Это просто другой случай, не обсуждаемый здесь и заслуживающий отедльной заметки.
Тут еще есть момент оптимизации. Если инициализировать объект в теле конструктора, то будет происходить изменение уже созданного и инициализированного (возможно, по умолчанию) объекта. Т.е. в некоторых случаях возможна даже двойная нагрузка при создании одного объекта. А конструкция типа A(int x): vectorSize(x+1), vector(vectorSize) {} инициализирует vector сразу при выделении под него память.
по сути это микрооптимизация и нужна только в очень редких случаях (несогласные идут читать Кнута, Александреску и Саттера)…
Герб Саттер, Андрей Александреску. Стандарты программирования на С++. Глава 48: «В конструкторах предпочитайте инициализацию присваиванию»:

В конструкторах использование инициализации вместо присваивания… предохраняет от ненужной работы времени выполнения ...


А инициализация переменных-членов — совсем не редкость. Кроме того:

Эта методика не является преждевременной оптимизацией; это — избежание преждевременной пессимизации


Благодарствую, крайне познавательный материал. Стандарт большой, наизусть не выучишь. Лично я этого «нюанса» не знал :)
>Стандарт большой, наизусть не выучишь. Лично я этого «нюанса» не знал :)

Стандарта можешь ты не знать, но книжки Саттера чтить обязан.
Написали бы заодно про базовые
class A { public: A(int v) : x(v) {} int x; };
class B
{
public:
  B(A * p) { p->x = 0; }
};

class C : public B, public A
{
public:
  C() : A(5), B(this)
  {
    std::cout << x; // ?
  }
};
Да я уж вижу, что писать про это можно :-)
Если будете писать про вызов конструкторов базовых классов, не забудьте про виртуальное наследование.
Писал я, было время, пару статеек под «громким» названием «С++ Mythbusters» и других подбивал поддержать тему. Но вместо того чтобы вникнуть в суть идеи таких статей все начали придираться к названию и тыкать в нос «а вот Александреску об этом писал» или «в стандарте это есть» и т.п.
Так к чему это я… Ваша статья как раз отлично подходит под это дело ;)

Вы её хотя бы в блог С++ перенесите.
Перенёс.

А про всяких тыкателей… я вижу два резона на то, что бы не заморачиваться по этому поводу.

1) На мой взгляд, статья не обязана быть интересна всем. Если она окажется интересна 5% читателей — это уже отлично. Ну кому-то не нравится… пусть не читает или напишет так, как нравится ему. В этом, как мне кажется, нет ничего страшного.

2) Нужно учитывать природу минусяторов и относиться к ним снисходительно. Большинство минусяторов ничем особо не интересуется (кроме, пожалуй, вопросов тайм-менеджмента, сводящихся к «чем же наконец заняться-то?»). Так как минусятору очень скучно и нечего делать, он слоняется по хабру и прочим интернетам, но нигде не находит ничего, что его бы потешило. Это бездарное времяпрепровождение делает его нервным, вспыльчивым и социально активным. И тут-то он начинает всех минусовать :-)

Я верю, что существует множество неминусяторов. Они не очень заметны потому, что читают хабр не каждый день и пишут комменты не каждую неделю :-) Но они совершенно вменяемы и им-то как раз были бы интересны статьи, выходящие за рамки темы «как же закрыть аську и делом-то наконец заняться?» :-)

Так к чему это я… не опускайте руки! :-)
UFO just landed and posted this here
Да вроде и не было сказано, что читающие стандарты попадают в 95% и что 95% — мудаки.
Повторение — мать учения :) Нужно читать и стандарт, и такие статейки.

И, да… Никто не говорил, что эти 95% — мудаки.
UFO just landed and posted this here
Погодите! :-)
Нет никакой связи между миносяторами и теми, кому не интересна статья. Первые — это дейстительно… э… закомплексованные люди. Вторые могут быть вполне вменяемыми людьми, просто они не программируют на с++. Эти множества никак не связаны… это как круглое и красное :-)
При этом му[yes]ки — это третий класс, про который тут речи нет вообще.
UFO just landed and posted this here
Всё очень просто. Достаточно сознательные люди и минусы ставят «за дело». Но! Большинство же напротив оценивает статью крайне не объективно, то бишь минусы они ставят с позиции «нравится ли лично мне», а не с позиции «полезна ли эта статья в целом»:
  1. Некоторые ставят минусы за то, что им не нравится стиль изложения материала (много либо, наоборот, мало юмора, «неправдоподобные» примеры и т.д.).
  2. Другие начинают оффтопить на тему несоответствия названия статьи её содержанию (исходя из их логики) или же вообще на темы типа:
    • «Правильно писать не „в Украине”, а „на Украине”, потому что слово „Украина” произошло от …, и у Розенталя сказано „…”, поэтому … [и понеслась…]»;
    • «О Боже милостивый! У Вас тире не длинное, а кавычки — не „ёлочки”! Это ставит с ног на голову все основы типографики. Да ведь ТотСамиЗнаетеКтоНоИмяКоторогоНельзяНазыватьНаХабреПотомуЧтоТакКручеИВообщеБольшинствоТакДелаетПоэтомуЯБудуПовторятьЗаВсеми упал бы с унитаза, если бы увидел это».
    • Прочий троллинг в том же духе
  3. Третьи мнят себя излишне умными и вечно, пардон, «впаривают», что у Саттера, Александреску, Мейерса, Страуструпа, Фрейда, Сиддхартхи Гаутамы, Иешуа, Магистра Йоды или Темного Властелина Саурона это в книгах уже было. Такие люди всегда знают наизусь имена всех оленей Санты и даже Коран целиком — что уж тогда говорить про стандарт С++. Но они никогда не учитывают, что сколько бы книг ни было написано, всегда найдется человек, который ни об этих книгах, ни об авторах не слышал, а для чтения стандарта банально не знает английский язык.
  4. К четвертой группе относятся те, кто живет с девизом «Я прогаю на самом лучшем, самом мегапопулярном, самом продуманном, мощном, простом для восприятия, используемом еще более 9000 таких же, как я, языке PHP (Python, СиШлак, Ruby, Javascript etc. — нужное подставить), а С++ пережиток прошлого, ни одного нового проекта на нем уже нету, кроме того для его понимания нужно знать, что в компьютере есть память, и (О боги мира! Спасите меня!!!) её нужно ВРУЧНУЮ выделять и очищать, а еще в универе С++ вёл дедуля, полковник в отставке, а PHP — симпатичная девчушка, выпустившаяся на 2 года раньше, а ныне купившаяся на улыбку ради зачета. Поэтому, чтобы этот беспредел не продолжался, чтобы новые, подрастающие поколения не дай бог не выбрали «бородатый» сишный путь, я выполню свою священную миссию и влеплю минус дабы сбить рейтинг!»
  5. К последей группе относятся те, кто следует за большинством и ставит минус вслед за предыдущими четырьмя. Это самые унылые люди в следствие своей несознательности.


Надеюсь, ничего не забыл ;)
UFO just landed and posted this here
UFO just landed and posted this here
Про «память» — это я утрированно ;) И в целом не бывает хороших и плохих языков.

По поводу умолчаний касательно людей, ставящих минус, ты прав наполовину. Просто исторически так сложилось, и все к этому привыкли, что большинство минусов «от фонаря», хотя однозначно бывают и исключения.

А по поводу «близко к сердцу» ты вообще погорячился :) Это всего лишь мой вариант троллинга.
UFO just landed and posted this here
Именно, а про такие вещи даже у Шилдта написано, но для новичков статья — очень даже ничего. Вот только она не полная, ибо есть еще туча примеров про инициализацию полей класса, список инициализации, список инициализации vs инициализация в теле конструктора и т.д., которые можно было раскрыть в этой статье.
Помнится мне, про Шилдта Вы довольно негативно отзывались. А сами, видно, тоже через него прошли — и ничего ;)

Я думаю, целью данной статьи не было полное описание способов инициализации — не учебник ведь в конце концов. Автор обратил внимание на маленький и не совсем очевидный нюанс языка, а человек мыслящий по прочтении данной статьи и сам сможет додумать, что из этого выплывает (или в крайнем случае найдет, где прочитать подробнее).
Зря Вы перестали. Статьи были интересные и подтолкнули меня к изучению c++ :)
Да я не перестал. У меня был творческий перерыв ;)))
>То вы обманываетесь
Может всетаки ошибаетесь :)
обманываетесь поэтичнее :-)
Знать всё это, вне всяких сомнений, полезно и важно. Писать код, в котором имеют место такие извороты — ни в коем случае.
У меня за 10 лет было 3-4 случая, когда такая инициализация была необходима, были на то причины. Не стоит зарекаться.
А у меня за те же 10 лет не было ни одного да и представить себе с трудом могу, почему они могли бы быть:
1) Что мешает инициализировать переменные независимо друг от друга тем самым входным параметром конструктора?
2) Что мешает вынести переменные, которые должны инициализироваться раньше в базовый класс и явно вызывать его конструкторы из конструкторов наследников?
3) Что мешает инициализировать первоочередные переменные в конструкторе, а те, которые должны просчитываться позже — в какой-нибудь процедуре Initialize()

— придумано за 15 секунд. Минут за 10, думаю, еще способа 3-4 избежать такого неадеквата можно найти легко.
> Что это? Баг в С++?

Нет, очередная неоднозначность и запутанность, за что этот язык многие и не любят.
Неоднозначность — это unspecified или undefined behaviour. А тут как раз всё однозначно, порядок вызова конструкторов всегда определён.
Уверен, в PHP, к примеру, тоже хватает своих нюансов.
Хватает. Но в Си их больше. там слишком много свободы дается программисту, можно писать всякие извраты, вроде a+++++b или директив препроцессора define, а это плохо, является источником потенциальных ошибок. Особенно все плохо в Си++: итераторы, шаблоны, мозг можно сломать. Как то слишком уж абстрактно все.

Язык (синтаксис), я считаю, должен быть как можно проще и недвусмысленней, чтобы с 1 взгляда было понятно что делает код, а не как тот же Перл где типичная команда выгллядит как $_~=s/\$\\'/'/g ну или типа того, и хрен что поймешь :(

К тому же простой язык снижает порог вхождения, а это выгодно.

При всем уважении к возможностям Си/Си++, после работы с более высокоуровненвыми я.п., врозвращаться к ручному выделению памяти (или извратно прикрученному сборщику мусора, что пожалуй хуже), не хочется.

Ах да, еще Си просто устарел — там до сих пор есть дурацкие требования определять функцию ДО ее использования, и сложная система #include. Хотя вроде есть язык D, призванный исправить подобные недостатки))
Sign up to leave a comment.

Articles