Использование виртуальных функций в конструкторе.

Angry
Уже с Приветом
Posts: 1491
Joined: 02 Jul 2003 22:47

Использование виртуальных функций в конструкторе.

Post by Angry »

Как-то наткнулся на неприятную особенность с++ (воистину, неисчерпаемый язык! :) ) - невозможность использования виртуальных функций в конструкторе. Код, конечно откомпилируется, но в наследниках конструктор выполнятся будет используя функции суперукласса, а не самого наследника.
Интересно, как логически объяснить этот феномен. Я так понимаю, таблица виртуальных функций заполняется после отработки конструктора. Но зачем так сделано?
Hamster
Уже с Приветом
Posts: 11475
Joined: 20 Nov 2000 10:01
Location: Escondido, CA

Post by Hamster »

Можно посмотреть тут:

http://msdn.microsoft.com/library/defau ... uctors.asp

Если у вас есть

Code: Select all

class А
{
public:
A();
virtual void function();
};

class B: public A
{
public:
B();
virtual void function();
};


К моменту отработки конструктора A объект B еще полностью не инициализирован, вызывать его функции опасно.
shadow7256
Уже с Приветом
Posts: 9402
Joined: 18 Mar 2004 15:11
Location: New York -> FL

Post by shadow7256 »

Нормальная практика. А как вызывать методы у еще не до конца достроенного объекта?

Мало ли что эти методы делают с членами класса (которых еще нет и в помине может быть) и от чего завивисит поведение внутри метода :umnik1:
Angry
Уже с Приветом
Posts: 1491
Joined: 02 Jul 2003 22:47

Post by Angry »

Я понял уже, что опасно. Вопрос, зачем так сделано проектировщиками языка?

Например, у меня есть базовый класс, который может инициализоватся совершенно по-разному. Я включаю в конструктор виртуальные функции, что бы в будущем через перегрузку этих функций решить эту проблему. Но таким способом эта проблема нерешаема. Как же поступить?
User avatar
Sabina
Уже с Приветом
Posts: 5669
Joined: 13 Oct 2000 09:01
Location: East Bay, CA

Re: Использование виртуальных функций в конструкторе.

Post by Sabina »

Angry wrote: Я так понимаю, таблица виртуальных функций заполняется после отработки конструктора. Но зачем так сделано?


Насколько я понимаю виртуальные функции, они и были созданы для того, чтобы при вызове этой функции для базового класса, программа динамически (at run time) определяла какую именно функцию derived class-а с таким же именем вызывать.

При этом в каждом derived class-е этот метод соответственно должен быть overrriden.

Сабина
Angry
Уже с Приветом
Posts: 1491
Joined: 02 Jul 2003 22:47

Post by Angry »

shadow7256 wrote:Нормальная практика. А как вызывать методы у еще не до конца достроенного объекта?

Мало ли что эти методы делают с членами класса (которых еще нет и в помине может быть) и от чего завивисит поведение внутри метода :umnik1:


Почему же вначале выделяется память под члены класс,отрабатывется код внутри конструктора, а потом заполняется VFT, хотя более логичным и удобным кажется выделение памяти, заполнение VFT, а потом уже отработка кода?
User avatar
Sabina
Уже с Приветом
Posts: 5669
Joined: 13 Oct 2000 09:01
Location: East Bay, CA

Post by Sabina »

Angry wrote:Я включаю в конструктор виртуальные функции, что бы в будущем через перегрузку этих функций решить эту проблему.


Там не overloading, а overriding.

Скажем у вас есть базовый класс Shape с функцией draw. Когда вы будете имплементировать Circle, Rectangular, Oval, YouNameIt эта функция будет переписана под каждый отдельный объект и вызываться будет именно она .

Сабина
Last edited by Sabina on 06 Jul 2004 23:04, edited 1 time in total.
ig
Уже с Приветом
Posts: 491
Joined: 09 Apr 2000 09:01
Location: Tigard, OR

Post by ig »

Потому что объект ещё не достроен.
Смотрите link
Last edited by ig on 06 Jul 2004 23:24, edited 1 time in total.
Angry
Уже с Приветом
Posts: 1491
Joined: 02 Jul 2003 22:47

Post by Angry »

ig wrote:Потому что класс ещё не достроен.
Смотрите link


Я же говорю, что это я понял.
Но кто-то может пояснить, почему так? Почему класс не достраивается до окончания работы конструктора? Чем это лучше, если бы все выделения памяти и заполнения VFT завершались до выполнения кода внутри конструктора?
Hamster
Уже с Приветом
Posts: 11475
Joined: 20 Nov 2000 10:01
Location: Escondido, CA

Post by Hamster »

Angry wrote: Я же говорю, что это я понял.
Но кто-то может пояснить, почему так? Почему класс не достраивается до окончания работы конструктора? Чем это лучше, если бы все выделения памяти и заполнения VFT завершались до выполнения кода внутри конструктора?


Порядок по-моему такой:

- Выделяется память
- VFT заполняется указателями на функции базового класса ( в моем примере, класс А )
- Вызывается конструктор A::A
- VFT перезаписывается указателями на функции субкласса ( B )
- Вызывается конструктор B::B

Вам придется вводить несколько конструкторов и вызывать в разных случаях разные конструкторы, или передавать какой-нибудь флаг, чтобы сказать конструктору, что ему делать.
shadow7256
Уже с Приветом
Posts: 9402
Joined: 18 Mar 2004 15:11
Location: New York -> FL

Post by shadow7256 »

Создание объекта не заключается только в выделении памяти и заполнении таблицы виртуальных функций. Выделить память под объект несложно, но как узнать как эта память будет использоватся и кем (какими переменными) ?

Страуструп сделал правильную вещь, запретив использовать полиморфизм, когда дело касается конструкторов, иначе геморроя было бы гораздо больше.

Используйте паттерн "Фабричный метод". Он также известен под именем "Виртуальный констуктор". Он безопасен, элегантен и сделает то, что Вам надо.
User avatar
tengiz
Уже с Приветом
Posts: 4468
Joined: 21 Sep 2000 09:01
Location: Sammamish, WA

Post by tengiz »

Angry wrote:Но кто-то может пояснить, почему так? Почему класс не достраивается до окончания работы конструктора? Чем это лучше, если бы все выделения памяти и заполнения VFT завершались до выполнения кода внутри конструктора?

Потому, что выделить память и "заполнть VFT" (на самом деле заполняется указатель на VFT, который обчыно назвается vptr, - а VFT одна на класс, а не одна на экземпляр класса) - это ещё не значит построить объект. Построить объект - это выделить под него память, заполнить vptr и выполнить его конструктор. При создании объекта, полученного наследованием, память выделяется под весь объект один раз, но заполнение указателся на vptr и вызов констуктора подкласса делается для каждого класса в иерархии. Т.е. для упомянутого примера:

1. Выделили память под B (которая, разумеется, включает всё, что нужно для A).
2. Заполнили vptr указателем на A::VFT.
3. Вызвали A::A.
4. Заполнили vptr указателем на B::VFT.
5. Вызвали B::B.

Ваш вопрос, по сути, сводится к следующему - А чем это лучше вот такого порядка инициализации:

1. Выделили память под B.
2. Заполнили vptr указателем на B::VFT.
3. Вызвали A::A.
4. Вызвали B::B.

Ответ: ничем. Кроме того, что вариант, выбранный дизайнерами языка несколько более предсказуем и логичен в случае, если виртуальная функция f, переопределённая в B, использует какие-то члены родительского класса, справедливо рассчитывая, что A-то уже правильно проинициализирован.
Cheers
ig
Уже с Приветом
Posts: 491
Joined: 09 Apr 2000 09:01
Location: Tigard, OR

Post by ig »

Хочу привести пример. Если бы полиморфизм был бы доступен в конструкторе, то было бы невозможно гарантировать что произойдёт при вызове виртуальной функции. Кто-то наследует Ваш класс и overrides виртуальную функцию которую Ваш конструктор вызывает. Никаких гарантий, может быть всё что угодно.

Это как раз то, что Вы пытаетесь сделать с базовым классом. Как уже посоветовали, лучьше пользоваться factory.

Удачи
8K
Уже с Приветом
Posts: 5552
Joined: 20 Mar 2001 10:01
Location: SFBA

Re: Использование виртуальных функций в конструкторе.

Post by 8K »

Angry wrote:Как-то наткнулся на неприятную особенность с++ (воистину, неисчерпаемый язык! :) ) - невозможность использования виртуальных функций в конструкторе.

Про C# почитайте, как там конструкторы и инициализаторы работают.
Увидев друга, Портос вскрикнул от радости...
User avatar
Boriskin
Уже с Приветом
Posts: 18906
Joined: 30 Aug 2001 09:01
Location: 3rd planet

Re: Использование виртуальных функций в конструкторе.

Post by Boriskin »

Angry wrote: Интересно, как логически объяснить этот феномен.


Порядком создания объекта, а именно порядком вызовов конструкторов базовых классов и (что в данном конкретном случае не так важно) конствукторов переменных - членов класса.

ЗЫ Именно то, о чем Тенгиз написал.
Тупизна как Энтропия. Неумолимо растет.

Return to “Вопросы и новости IT”