#destructor
#деструктор
Вопрос:
Недавно я присутствовал на собеседовании, где человек спросил меня, каков порядок построения и уничтожения. Я объяснил, что построение происходило от базы к дочернему элементу и разрушение от дочернего элемента к базовому.
Интервьюер был заинтересован в том, чтобы узнать, есть ли какая-то особая причина для уничтожения, происходящего из derived в base. Я объяснил ему, но он не был убежден.
Его точка зрения заключалась в том, что если уничтожение базового класса приведет к исключению, как производный класс узнает, что объект производного класса уже будет уничтожен.
Он также говорил, что производный класс содержит члены базового класса, так почему мы не можем сначала вызвать уничтожение базового класса?
Я объяснил, что после завершения уничтожения производного класса мы не можем сказать, что объект полностью уничтожен.
Я прав здесь? каков наилучший ответ здесь?
Комментарии:
1. Это о C ? Тогда, пожалуйста, пометьте его соответствующим образом. Не все языки используют тот же порядок построения, что и C .
2. ПРИМЕЧАНИЕ: Это было интервью на C #, но интервьюер попросил меня рассказать в целом, предполагая, что у меня есть деструктор в классе
3. В C # инициализаторы запускаются перед конструкторами и в противоположном «направлении», начиная с инициализаторов для самого производного класса: blogs.msdn.com/b/ericlippert/archive/2008/02/18 /…
4. Если разрушение произошло наоборот, от base к derived, то почему мы не можем сказать, что объект полностью уничтожен после завершения уничтожения производного класса? На этом этапе разрушение произошло бы на каждом уровне…
Ответ №1:
В C # построение объектов выполняется в следующем порядке:
- Инициализация элементов данных Derived -> Base
- Выполнение конструкторов Base -> Derived .
Почему?
- Инициализация следует за Derived -> Base, чтобы избежать повторной инициализации элементов, инициализированных по-разному в производных от base.
- Построение следует за Base -> Derived, потому что производный конструктор может полагаться на методы базового класса.
Другие языки обрабатывают инициализацию по-разному, но конструкция Base-> Derived является типичной.
Уничтожение выполняется из Derived -> Base .
Почему?
-
Производный может по-прежнему полагаться на ресурсы, выделенные базой во время уничтожения.
- Если бы база была уничтожена первой, эти ресурсы больше не были бы доступны для Derived .
-
Каждый уровень иерархии должен отвечать за освобождение любых ресурсов, выделенных этим уровнем.
- Base не удалось освободить ресурсы производного класса, он о них не знает.
- Derived также не должен отвечать за ресурсы, выделенные базой
- В строгом ООП производный не должен даже знать подробности об этих ресурсах или о том, каким образом они выделяются или освобождаются.
Чтобы ответить на его конкретные вопросы:
- Исключение в базе
- Исключения не должны создаваться в деструкторах (в частности, в C # это, по крайней мере, за один раз, может убить сборщик мусора)
- Производные классы содержат члены базового класса
- Объект имеет один экземпляр любого элемента данных. Не по одному на уровень иерархии.
Итак, мы возвращаемся к если base освобождает ресурсы, связанные с элементом данных, он больше не будет доступен для использования производным классом.
- Объект имеет один экземпляр любого элемента данных. Не по одному на уровень иерархии.
К вашему объяснению обоснования заказа. Если мы предположим, что порядок Derived->Base правильный, это объясняет, почему вы все равно должны вызывать базовый деструктор, но не почему Derived стоит на первом месте.
Если бы проблемы, касающиеся порядка, были другими, и уничтожение было завершено Base-> Derived, тогда мы могли бы считать объект полностью уничтоженным после завершения производного деструктора, поскольку уничтожение произошло бы на всех уровнях.
Комментарии:
1. Г-н Миндор, я хотел бы спросить об этой строке здесь: Инициализация следует за Derived -> Base, чтобы избежать повторной инициализации членов, инициализированных по-разному в Derived из base . зачем им повторно инициализироваться, если два инициализируют совершенно разные элементы?
2. @EricMovsessian Это было бы в том случае, если производный класс переопределяет инициализацию члена, существующего в base. Если два инициализируют совершенно разные элементы, не будет возможности что-либо повторно инициализировать.
3. Мистер Миндор, большое вам спасибо за обращение. Я так боюсь, что не понимаю, что вы подразумеваете под переопределением инициализации. Не могли бы вы помочь?
4. На самом деле, я думаю, что вижу это сейчас. Так что в основном в тех случаях, когда вы будете использовать одно и то же имя для члена, который находится как в базовом, так и в производных классах. Значит ли это, что когда элемент в derived инициализируется, и компилятор обращается к другому элементу в base с тем же именем, он как бы игнорирует его?
Ответ №2:
Вы абсолютно правы. Я часто задавал этот вопрос при собеседовании с опытными программистами на C . Производный класс должен быть создан после базового класса, чтобы конструктор производного класса мог ссылаться на данные базового класса. По той же причине деструктор производного класса должен выполняться перед деструктором базового класса. Это очень логично: мы создаем изнутри, а уничтожаем снаружи. Если деструктор базового класса выдает исключение, оно не может быть перехвачено деструктором производного класса. Также исключение в конструкторе базового класса не может быть обработано конструктором производного класса. В общем, исключения не должны создаваться из деструкторов.
Ответ №3:
Важно понимать, что построение объекта происходит поэтапно. Если у вас есть класс B, производный от A, вы создаете B, сначала создавая A, а затем превращая A в B. Аналогично, B уничтожается, сначала превращая его в A, а затем уничтожая A. Это обеспечивает очень последовательный способ мышления о создании и уничтожении объектов.