![]() |
Модераторы: bsa |
![]() ![]() ![]() |
|
Thunderbolt |
|
||||||||||||||||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 6 Всего: 16 |
Захотелось написать несколько небольших заметок о том, как программисты на Си/Си++ играют с огнём, не подозревая об этом. Первая заметка будет про попытки явно вызвать конструктор.
Программисты - ленивые существа. Поэтому норовят решить задачу минимальным количеством кода. Это похвальное и хорошее стремление. Главное не увлечься процессом и вовремя остановиться. Например, программистам бывает лень создавать единую функцию инициализации в классе, чтобы затем вызывать её из разных конструкторов. Программист думает: "Зачем мне лишняя функция? Я лучше вызову один конструктор из другого". К сожалению, даже эту простую задачу программисту удается решить не всегда. Для выявлений таких неудачных попыток я как раз сейчас реализовываю в PVS-Studio новое правило. Вот, пример кода, который я обнаружил в проекте eMule:
Рассмотрим внимательнее реализацию последнего конструктора. Программист решил, что код
просто вызывает другой констурктор. Ничего подобного. Здесь создается и тут же уничтожается новый неименованный объект типа CSlideBarGroup. Получается, что программист действительно вызвал другой конструктор. Вот только сделал он совсем не то, что задумал. Поля класса останутся неинициализированными. Такие ошибки, это только половина беды. Некоторые знают, как все-таки действительно вызвать другой конструктор. И вызывают. Лучше бы они не знали, как это делается. ![]() Например, приведенный код, можно было бы переписать так:
или так:
Теперь действительно один конструктор для инициализации данных вызывает другой конструктор. Если увидите, программиста, который так делает, отвесьте ему один шелбан в лоб от себя и один от меня лично. Приведенные примеры являются очень опасным кодом, и нужно хорошо понимать, как они работают! Из-за мелочной оптимизации (лень писать отдельную функцию), этот код может нанести больше вреда, чем пользы. Рассмотрим подробнее, почему иногда подобные конструкции работают, но чаще нет.
Этот код будет корректно работать. Код безопасен и работает, так как класс содержит простые типы данных и не наследуется от других классов. В этом случае двойной вызов конструктора ничем не грозит. Рассмотрим другой код, где явный вызов конструктора приводит к ошибке (пример взят из дискуссии на сайте StackOverflow):
Когда мы вызываем конструктор "new (this) Derived(bar.foo);", объект Base уже создан и поля инициализированы. Повторный вызов конструктора приведет к двойной инициализации. В 'ptr' запишем указатель на вновь выделенный участок памяти. В результате получаем утечку памяти. К чему приведет двойная инициализация объекта типа std::vector, вообще предсказать сложно. Ясно одно. Такой код недопустим. Вывод Явный вызов конструктора требуется только в крайне редких случаях. В обычном программировании, явный вызов конструктора, как правило, появляется из-за желания сокращения размера кода. Не надо этого делать! Создайте обыкновенную функцию инициализации. Вот как должен выглядеть правильный код:
P.S. Явный вызов одного конструктора из другого в C++11 (делегация) Новый стандарт С++11 позволяет вызывать одни конструкторы класса из других (так называемая делегация). Это позволяет писать конструкторы, использующие поведение других конструкторов без внесения дублирующего кода. Пример корректного кода:
--------------------
Карпов Андрей, DevRel в PVS-Studio. |
||||||||||||||||
|
|||||||||||||||||
boostcoder |
|
||||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 20 Всего: 110 |
Thunderbolt, спасибо огромное за очередную статью
![]() по поводу делегирующих конструкторов, есть такой, не очевидный момент о котором многие и не подозревают:
в приведеном коде, деструктор будет вызван в любом случае.
Добавлено через 7 минут и 42 секунды зы на самом деле, никогда не возникало мысли, из конструктора вызвать конструктор ![]() |
||||
|
|||||
bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 85 Всего: 196 |
Как раз этот момент абсолютно очевиден - любой конструктор класса полностью инициализирует объект и вызов для него деструктора необходим. Это свойство значительно увеличивает безопасность исключений кода по сравнению с общей функцией инициализации. |
|||
|
||||
boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 20 Всего: 110 |
два вопроса. 1. разве обязательно? 2. что происходит с деструктором если в конструкторе выбрасывается исключение? |
|||
|
||||
disputant |
|
|||
![]() Бывалый ![]() Профиль Группа: Участник Сообщений: 210 Регистрация: 28.11.2011 Репутация: нет Всего: 3 |
А вот с чисто теоретической точки зрения - внутри конструктора формально объект еще не создан, так имеем ли мы формальное право вызывать в нем функцию-член?... |
|||
|
||||
boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 20 Всего: 110 |
объект создан. а инициализирован ли он - это уже другой вопрос. |
|||
|
||||
disputant |
|
|||
![]() Бывалый ![]() Профиль Группа: Участник Сообщений: 210 Регистрация: 28.11.2011 Репутация: нет Всего: 3 |
Лень на ночь глядя рыться, но то ли у Страуструпа, то ли у Саттера этот вопрос поднимался и говорилось, насколько я помню, что формально так поступать нельзя, другое дело, что неформально любой компилятор это пропускает ![]() Выделена память - это да, базовый объект создан - да, но объект текущего типа - нет. Кажется, так. Это сообщение отредактировал(а) disputant - 30.1.2012, 23:31 |
|||
|
||||
newbee |
|
|||
![]() Бревно ![]() ![]() Профиль Группа: Участник Сообщений: 703 Регистрация: 24.8.2011 Репутация: 3 Всего: 19 |
-------------------- You're face to face With man who sold the world |
|||
|
||||
volatile |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2107 Регистрация: 7.1.2011 Репутация: 16 Всего: 85 |
Вопрос действительно актуальный, я пару раз обжегся с такими вызовами.
С тех пор всегда создаю init (..); и вызываю его из конструкторов. Даже если один конструктор. ![]() обжегшись на молоке, дуешь на воду. |
|||
|
||||
boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 20 Всего: 110 |
именно так. все еще надеюсь, bsa пояснит свой ответ. а то жутко интересно. |
|||
|
||||
Result |
|
||||
Шустрый ![]() Профиль Группа: Участник Сообщений: 52 Регистрация: 15.5.2011 Репутация: 3 Всего: 5 |
Не поделишься ли источником информации ? К примеру в одной книжке написано
В примере Бусткодера один конструктор полностью отрабатывает, поэтому, как мне видится, объект и считается полностью сконструированным. И дело не в списке инициализации. И в конструкторе также может быть просто выделение памяти, скажем размещающим оператором new, а не только инициализация. |
||||
|
|||||
Thunderbolt |
|
|||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 6 Всего: 16 |
По поводу делегирующих конструкторов:
Следует заметить, что если в C++03 объект считается до конца созданным когда его конструктор завершает выполнение, то в C++11 после выполнения хотя бы одного делегирующего конструктора остальные конструкторы будут работать уже над полностью сконструированным объектом. Несмотря на это объекты производного класса начнут конструироваться только после выполнения всех конструкторов базовых классов. Взято из Wikipedia. Улучшение конструкторов объектов.. --------------------
Карпов Андрей, DevRel в PVS-Studio. |
|||
|
||||
bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 85 Всего: 196 |
boostcoder, за меня уже полностью отдулись Result и Thunderbolt. Именно это я и имел в виду.
|
|||
|
||||
boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 20 Всего: 110 |
bsa, необычность в том, что делегирующий конструктор - это тоже конструктор, а не функция init
Добавлено через 4 минуты и 44 секунды не помню откуда знаю это. но это факт. и тело конструктора к этому факту никак не относится. докажи обратное. |
|||
|
||||
newbee |
|
|||
![]() Бревно ![]() ![]() Профиль Группа: Участник Сообщений: 703 Регистрация: 24.8.2011 Репутация: 3 Всего: 19 |
Смотри, до finally происходит инициализация всех членов объекта. После этого в конструкторе происходит настройка объекта. По стандарту все это вместе называется инициализацией объекта. Тут я обожглась на терминологии. Выше был вопрос "можно ли вызывать методы внутри конструктора, ведь он еще не создан" - я отвечала на него, именно в этом контексте были мои слова о честной инициализации: в конструкторе рамки объекта уже четко определены, члены инициализированы, значит вызывать обычные методы (читай функции) можно. НО. Из-за наркоманского дизайна языка можно напороться на несколько косяков, связанным как раз с тем, что объект еще недосоздан. Во-первых, выброшенное из конструктора или где-нибудь глубже по стеку вызова исключение приведет к тому, что деструктор объекта вызван не будет -> нужно несколько раз подумать прежде чем выделять внутри конструктора память, открывать файловые дескрипторы и т.д. и , самая пичалька, при необходимости самому бросить исключение из конструктора придется дублировать код деструктора или выносить его в отдельную функцию. Во-вторых, вызов виртуальной функции из конструктора обернется вызовом функции этого класса, а не перекрытого. По этим причинам часто имеет место ручная двухуровневая инициализация объекта: сначала создают объект, потом вызывают в нем метод init. В частности из-за этого я считаю объектную систему С++ кривой как моя жизнь и предпочитаю процедурно-функциональный стиль, если приходится на нем писать. -------------------- You're face to face With man who sold the world |
|||
|
||||
bems |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 3400 Регистрация: 5.1.2006 Репутация: нет Всего: 88 |
шикарно улучшили -------------------- Обижено школьников: 8 |
|||
|
||||
mes |
|
|||
любитель ![]() ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 7954 Регистрация: 14.1.2006 Репутация: 79 Всего: 250 |
см. RAII понятие объект шире, чем совокупность его членов.. Поэтому инициализация всех членов не является достаточным условием полностью сконструированного объекта.. |
|||
|
||||
newbee |
|
|||
![]() Бревно ![]() ![]() Профиль Группа: Участник Сообщений: 703 Регистрация: 24.8.2011 Репутация: 3 Всего: 19 |
И в этом все С++ники, подобрали решение и делают вид, что проблемы не существует.
-------------------- You're face to face With man who sold the world |
|||
|
||||
bsa |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 9185 Регистрация: 6.4.2006 Где: Москва, Россия Репутация: 85 Всего: 196 |
Это компромисс между надежностью и быстродействием.
|
|||
|
||||
mes |
|
|||
любитель ![]() ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 7954 Регистрация: 14.1.2006 Репутация: 79 Всего: 250 |
касательного данного случая проблема в том, что в С++ можно работать с голыми указателями ? ведь проблема не в выделении ресурсов, а именно в наготе последних.. В С проблемы нет, потому что все так и так возложено на программиста (как на ломовую лошадь).. а раз используешь автоматизацию С++, так следуй ей до конца.. Это сообщение отредактировал(а) mes - 2.2.2012, 11:42 |
|||
|
||||
![]() ![]() ![]() |
Правила форума "C/C++: Для новичков" | |
|
Запрещается! 1. Публиковать ссылки на вскрытые компоненты 2. Обсуждать взлом компонентов и делиться вскрытыми компонентами
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, JackYF, bsa. |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Для новичков | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |