managed C++: порядок действий в конструкторе
А как в Managed C++ обстоит дело с вызовом виртуальных функций из конструктора? Так же как и в C++, или они виртуально вызываются?
В .NET можно вызывать виртуальные функции из конструктора, хотя это и не рекомендуемая практика. Но это, по видимому, следствие порядка инициализации, а не причина.
Допустим есть некоторый "базовый" класс (Base который поддерживает какой-нибудь полезный интрефейс (ISmartInterface и "настраивается", получая в конструкторе по ссылке несколько объектов.
Тогда фича, о который ты пишешь, была бы удобна для изготовления "настроенного класса" (Tuned который бы наследовался от "базового",
а списком своих членов имел бы параметры для него.
Сам не использую managed С++, и вот как раз сейчас понял, что такая фича была бы мне иногда удобна. Передачей предка в конструкторы членов или членов - в конструкторы друг другу я занимаюсь редко, а вот использовать члены как параметры для базы иногда хочется.
class X1 { ...};
class X2 { ...};
class X3 { ...};
class ISmartInterface
{
public:
virtual void doIt = 0;
};
class Base
: public ISmartInterface
{
X1 &m_rArg1;
X2 &m_rArg2;
X3 &m_rArg3;
protected:
Base(X1 Arg1, X2 Arg2, X3 Arg3)
: m_rArg1(Arg1)
, m_rArg2(Arg2)
, m_rArg3(Arg3)
{
}
virtual void doIt
{
m_rArg1.doSmth1( ... );
m_rArg2.doSmth2( ..., m_rArg1, m_rArg3, ... );
m_rArg3.doSmth3( ...);
}
};
class Tuned
: public Base
{
X1 &m_A1;
X2 &m_A2;
X3 &m_A3;
public:
Tuned( ...,/*something non-trivial*/ , )
: m_A1( ...,/*something non-trivial*/,... )
, m_A2( ...,/*something non-trivial*/,...)
, m_A3( ...,/*something non-trivial*/,...)
, Base(m_A1,m_A2,m_A3) // ! I wish it is the last !
{
}
};
Да ну, это так тупо.
а вот использовать члены как параметры для базы иногда хочетсяТак вроде список инициализации позволяет ограниченно изменять порядок вызова конструторов.
По теме: базовый класс должен уметь конструироваться, не зная полей выведенного класса. А вот поля выведенного класса могут зависеть от полей базового. Так что порядок, описанный в стандарте мне кажется правильным.
Так вроде список инициализации позволяет ограниченно изменять порядок вызова конструторов.
В unmanaged C++ порядок инициализации полей определяется их порядком декларации в теле класса. При этом, конструктор базового класса ВСЕГДА вызывается раньше.
да, хуйню спорол... Хотя однажды хотелось чем-то подобным воспользоваться, но до попытки это закодить не дошло.
да, хуйню спорол... Хотя однажды хотелось чем-то подобным воспользоваться, но до попытки это закодить не дошло.ИМХО желание воспользоваться такой гипотетической возможностью — признак плохого дизайна.
ИМХО желание воспользоваться такой гипотетической возможностью — признак плохого дизайна.Всего-лишь рассуждал о том, как бы сделать на C++ аналог одного из решений, применяющихся в Delphi минимальными усилиями (Builder - это всё-таки не совсем стандартный C++). И кое-что получалось не совсем лучшим образом из-за того, что в Delphi гораздо более управляемый процесс конструирования объекта. А оценка правильности дизайна в цели моих размышлений не входила, однако замечу, что при создании такого удобного инструмента как Delphi, Borland'овцы на основе обычного Паскаля развили новый язык, на часть особенностей которого явно повлияли нужды архитектуры среды Delphi. В случае с Managed C++ скорее всего имеет место подобная ситуация.
ИМХО желание воспользоваться такой гипотетической возможностью — признак плохого дизайна.Что значит плохого? Того, который не учитывает заранее особенности языка?
Ну зачем, скажи на милость, нормальному человеку хотеть контролировать порядок инициализации полей у себя в классе таким вот образом? Жду адекватного примера.
Почему-то подумал, что ты это про этот самый альтернативный порядок инициализации база-наследник и наоборот. То, что поля должны быть независимы — это вроде очевидно.
То, что поля должны быть независимы — это вроде очевидно
Иногда может хотеться и обратного. Например, если некоторый класс A в конструкторе принимает ссылку на объект B, с которым будет работать, то имхо, вполне естественно создать другой класс C, который "настраивает" класс A нужным образом. А именно, заготавливает первым своим полем подходящий объект В, и "скармливает" его конструктору класса A, которое сидит вторым полем.
class B {....}; //some interface
class A //some interface utility
{
public:
explicit A(B & operand);
};
class B_impl: public B {...}; //concrete implementation of interface
class C
{
B_impl m_B;
A m_A; //the order is significant here !
public:
C
: m_B( ... /*some special initialization*//..)
, m_A(m_B)
{}
};
При этом я вполне понимаю, что закладываться на порядок инициализации полей довольно опасно - особенно когда класс сложный, и поля легко могут в процессе эволюции поменяться местами. Однако когда ситуация простая (то есть, класс С только и делает, что хранит поле B_Impl и отдает его в конструктор A то ихмо, этот пример вполне имеет право на существование.
Какие у меня возникли вопросы к неинтересному:
1) А почему класс C такой надуманный? Ну передавайте вы полю типа A в конструктор новый экземпляр B_impl, не храня его больше нигде. Ниоткуда не следует, что так нельзя делать (вы же не объяснили, почему так важно иметь в классе C это поле).
2) Почему вы нарушаете простое правило о том, какие типы классов как должны себя вести и как должны быть реализованы? Саттер с Александреску об этом писали в своих "Стандартах программирования на С++". Классы имеет смысл разделять на два вида — как бы неполиморфные и как бы полиморфные. Вот интерфейс (и его реализация) — очень полиморфные типы. Крайне полиморфные. Их надо по указателю хранить (а лучше по shared_ptr). А с указателями проблемы порядка инициализации (коли уж вам так надо, что мне до сих пор не понятно) не возникает никакой.
Ну передавайте вы полю типа A в конструктор новый экземпляр B_impl,1) не хорошо, когда когда класс В сложный и не имеет семантику значения, то есть копироваться может не так легко. У меня это бывают файлы, потоки, интерфейсы записей в лог, интерфейс редактирования треугольной "меши" (в духе, добавить/подвинуть/удалить вершину, добавить треугольник). Все это копировать бессмысленно.
2) к тому же А про B_impl ничего не знает, а знает только про абстрактный интерфейс B.
. Их надо по указателю хранить (а лучше по shared_ptr).Это мы скорее уходим к двум другим (отчасти, "философским") вопросам:
а) нужны ли вообще ссылки, когда в языке есть указатели.
б) надо ли что-нибудь инициализировать в списке инициализации конструктора, когда можно написать метод Initialize
Преимущества и недостатки есть и у того, и другого
решения (для каждого вопроса) - надо решать в зависимости от ситуации (например, ссылки нельзя забыть инициализировать и они всегда не нулевые (что есть плюс зато их инициализировать можно только в списке инициализации, что есть неудобство, и они чувствительны к порядку инициализации, что есть опасность; список инициализации имеет странный синтаксис (отличный от подряд идущих операторов в теле функции какие-то заморочки с порядком инициализации, зато нет вопросов типа "кто и когда должен вызывать Initialize?").
Я лишь хотел показать, что ситуации, в которых (имхо)надо использовать ссылки и пользоваться списком инициализацию в конструкторе вполне реальны.
А также (возвращаясь к началу топика что ситуации, где удобнее сначала инициализировать поля класса раньше базы - тоже вполне реальны.
Я лишь хотел показать, что ситуации, в которых (имхо)надо использовать ссылки и пользоваться списком инициализацию в конструкторе вполне реальны.
Так ведь не удалось.
1) В коде выше вы уже передавали в конструктор A новый экземпляр B_impl. Вся разница была в том, что это поле еще отдельно сохранялось с типом B_impl. А теперь B, оказывается, лишен семантики копирования. Или A у вас внутри ссылку на B сохраняет? Ну так храните и A и B_impl по указателю, а не по значению, и инициализируйте по-человечески в конструкторе.
2) И нигде не было обосновано, что классу C помимо всего прочего нужен не только настроенный экземпляр A, который обращается к B через интерфейс, но и деабстрагированный B_impl (зачем тогда вообще абстракция?). Короче, не жизненный пример.
б) надо ли что-нибудь инициализировать в списке инициализации конструктора, когда можно написать метод Initialize
Вот лично я к этому вопросу точно не уходил. Более того, он мне кажется странным. Основное применение метода Initialize — в сочетании с паттерном Template method для контроля этапов инициализации базового класса. При чем тут списки инициализации?
Вообще списки инициализации — крайне неестественная штука. Их, наверное, и сделали только для того, чтобы лишний раз оператор присваивания не вызывать для сложных типов, которые в классе хранятся по значению.
Оставить комментарий
Realist
Вопрос философического свойства: нафига в managed C++ поменяли порядок инициализации?Я сегодня на это наткнулся и смотрел дебагером как баран на новые ворота.
http://msdn.microsoft.com/en-us/library/aa985615.aspx
Для нормальный плюсов стандарт действительно определяет (12.6.2 p 197 для 1998 года) такой порядок:
— виртуальные предки
— предки
— члены
— тело конструктора.