Почему не вызывается перегруженная виртуальная функция?
От: Symon Россия  
Дата: 14.03.06 13:20
Оценка:
class A
{
public:
    virtual ~A ()
    {
        printf ("A destructed\n");
        End ();
    };
    virtual Start () = 0;
    virtual End ()
    {
        printf ("A ended\n");
    };
};

class B: public A
{
public:
    virtual ~B ()
    {
        printf ("B destructed\n");
    };
    virtual Start ()
    {
        printf ("B started\n");
    };
    virtual End ()
    {
        printf ("B ended\n");
    };
};

int _tmain(int argc, _TCHAR* argv[])
{
    B *b = new B;
    delete b;
    return 0;
}

B destructed
A destructed
A ended

Че это за бред такой???
Re: Почему не вызывается перегруженная виртуальная функция?
От: jazzer Россия Skype: enerjazzer
Дата: 14.03.06 13:23
Оценка: +3 :)
Здравствуйте, Symon, Вы писали:

S>Че это за бред такой???


А слабо было в поиске написать "виртуальная функция в деструкторе"?
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Почему не вызывается перегруженная виртуальная функция?
От: night beast СССР  
Дата: 14.03.06 13:25
Оценка: 1 (1)
Здравствуйте, Symon, Вы писали:

S>
S>class A
S>{
S>public:
S>    virtual ~A ()
S>    {
S>        printf ("A destructed\n");
S>        End ();
S>    };
S>    virtual Start () = 0;
S>    virtual End ()
S>    {
S>        printf ("A ended\n");
S>    };
S>};

S>class B: public A
S>{
S>public:
S>    virtual ~B ()
S>    {
S>        printf ("B destructed\n");
S>    };
S>    virtual Start ()
S>    {
S>        printf ("B started\n");
S>    };
S>    virtual End ()
S>    {
S>        printf ("B ended\n");
S>    };
S>};

S>int _tmain(int argc, _TCHAR* argv[])
S>{
S>    B *b = new B;
S>    delete b;
S>    return 0;
S>}
S>

S>B destructed
S>A destructed
S>A ended

S>Че это за бред такой???

а вот не надо из конструкторов/деструкторов вызывать виртуальные функции.
в контрукторе базового наследник еще не создан.
в деструкторе базового -- уже разрушен.
такие дела.
Re: Почему не вызывается перегруженная виртуальная функция?
От: Bell Россия  
Дата: 14.03.06 13:25
Оценка: 1 (1)
Здравствуйте, Symon, Вы писали:

S>Че это за бред такой???

Виртуальные вызовы не работают в конструкторах и деструкторе. Это обсуждалось не один раз.

ЗЫ
Тип возвращаемого значения функций лучше указывать — default int не поддерживается стандартом С++.
Любите книгу — источник знаний (с) М.Горький
Re[2]: Почему не вызывается перегруженная виртуальная функци
От: Symon Россия  
Дата: 14.03.06 13:28
Оценка: :)
Здравствуйте, jazzer, Вы писали:

J>Здравствуйте, Symon, Вы писали:


S>>Че это за бред такой???


J>А слабо было в поиске написать "виртуальная функция в деструкторе"?


Хорошо хоть не читайте Страуструпа
Re[2]: Почему не вызывается перегруженная виртуальная функци
От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
Дата: 14.03.06 13:43
Оценка:
Здравствуйте, Bell, Вы писали:

B>ЗЫ

B>Тип возвращаемого значения функций лучше указывать — default int не поддерживается стандартом С++.

Тем более, что в данном конкретном примере int не подходит — функции не возвращают значений

ЗЫЫ
А также незачем ставить точку с запятой после определения функции.
Re[3]: Почему не вызывается перегруженная виртуальная функци
От: jazzer Россия Skype: enerjazzer
Дата: 14.03.06 14:47
Оценка:
Здравствуйте, Symon, Вы писали:

J>>А слабо было в поиске написать "виртуальная функция в деструкторе"?


S>Хорошо хоть не читайте Страуструпа :)


Если бы ты задал этот вопрос ему, я думаю, такой ответ от него был бы вполне логичным.
Поиск на этом сайте сделан специально для того, чтобы людям не приходилось по сто раз отвечать на одни и те же вопросы.
Даже кнопочка есть: "Найти ответ".

Имей уважение к тем, к кому обращаешься за помощью, цени их время.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Почему не вызывается перегруженная виртуальная функци
От: Лазар Бешкенадзе СССР  
Дата: 14.03.06 16:39
Оценка: +1
Здравствуйте, Bell, Вы писали:

B>Здравствуйте, Symon, Вы писали:


S>>Че это за бред такой???

B>Виртуальные вызовы не работают в конструкторах и деструкторе. Это обсуждалось не один раз.

На самом деле работают, более того, работают именно как
виртуальные, просто вызывается последний override на момент
создания объекта класса исполняемого конструктора/деструктора.

Лазар
Re[3]: Почему не вызывается перегруженная виртуальная функци
От: Bell Россия  
Дата: 15.03.06 07:51
Оценка: +1
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>На самом деле работают, более того, работают именно как

ЛБ>виртуальные, просто вызывается последний override на момент
ЛБ>создания объекта класса исполняемого конструктора/деструктора.

В моем понимании фраза "работают именно как виртуальные" подразумевает, что в общем случае механизмы вызова виртуальной и "обычной" функции в конструкторе/деструкторе могут отличаться. Если именно это имелось ввиду, то нельзя ли привести пример, иллюстрирубщий это утверждение? Если нет, то фраза "работают именно как виртуальные" лишена смысла, ИМХО.
Я что-то упустил?
Любите книгу — источник знаний (с) М.Горький
Re[3]: Почему не вызывается перегруженная виртуальная функци
От: ekamaloff Великобритания  
Дата: 15.03.06 08:44
Оценка: 8 (1)
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>На самом деле работают, более того, работают именно как

ЛБ>виртуальные, просто вызывается последний override на момент
ЛБ>создания объекта класса исполняемого конструктора/деструктора.

Для интереса посмотрел, что делает MS VC++ 7.1:

class Base
{
public:
    virtual void Foo() { }
    void Bar() { }

    virtual ~Base()
    {
        Foo();  // 3
        Bar();  // 4
    }
};

class Derived: public Base
{
public:
    virtual void Foo() { }
};

int main()
{
    Base* pb = new Derived();
    pb->Foo();   // 1
    pb->Bar();   // 2
    delete pb;

    return 0;
}


Строчки (1) и (2) породили следующий машинный код:

    pb->Foo();
00411CFD  mov         eax,dword ptr [pb] 
00411D00  mov         edx,dword ptr [eax] 
00411D02  mov         esi,esp 
00411D04  mov         ecx,dword ptr [pb] 
00411D07  call        dword ptr [edx] 
00411D09  cmp         esi,esp 
00411D0B  call        @ILT+1165(__RTC_CheckEsp) (411492h) 
    pb->Bar();
00411D10  mov         ecx,dword ptr [pb] 
00411D13  call        Base::Bar (411609h)


Видно, что в первом случае вызов производится "виртуальный" вызов (т.е. адрес функции берется из таблицы виртуальных методов объекта), а во втором случае — "обычный", т.е. по известному заранее (на момент компиляции) адресу. В-общем ничего удивительного, все как и ожидается.

Теперь смотрим, что получилось из строчек (3), (4) (вызовов виртуальной и невиртуальной функций в деструкторе):

        Foo();
00411FDC  mov         ecx,dword ptr [this] 
00411FDF  call        Base::Foo (411523h) 
        Bar();
00411FE4  mov         ecx,dword ptr [this] 
00411FE7  call        Base::Bar (411609h)


Как видно из этого листинга, ни о каком виртуальном вызове (в случае с Foo), тут речи и не идет. Адрес берется не из vtable, а уже известен на момент компиляции. Поэтому фраза "работают именно как виртуальные", как справедливо заметил Bell, совершенно несправедлива.

Кстати если Foo сделать в классе Base чисто виртуальной, то получим ошибки линковки:

Win32Console error LNK2019: unresolved external symbol "public: virtual void __thiscall Base::Foo(void)" (?Foo@Base@@UAEXXZ) referenced in function "public: virtual __thiscall Base::~Base(void)" (??1Base@@UAE@XZ)


Если при этом убрать вызов Foo из деструктора Base, но оставить этот же вызов в main(), то ошибки нет.

В-общем все это ИМХО говорит о том, что ни о какой виртуальности вызовов в конструкторе/деструкторе и речи не идет.

ЗЫ: насколько это согласуется со стандартом, не знаю.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[4]: Почему не вызывается перегруженная виртуальная функци
От: Лазар Бешкенадзе СССР  
Дата: 15.03.06 08:44
Оценка:
Здравствуйте, Bell, Вы писали:

B>Здравствуйте, Лазар Бешкенадзе, Вы писали:


B>>>Виртуальные вызовы не работают в конструкторах и деструкторе. Это обсуждалось не один раз.


ЛБ>>На самом деле работают, более того, работают именно как

ЛБ>>виртуальные, просто вызывается последний override на момент
ЛБ>>создания объекта класса исполняемого конструктора/деструктора.

B>В моем понимании фраза "работают именно как виртуальные" подразумевает, что в общем случае механизмы вызова виртуальной и "обычной" функции в конструкторе/деструкторе могут отличаться. Если именно это имелось ввиду, то нельзя ли привести пример, иллюстрирубщий это утверждение? Если нет, то фраза "работают именно как виртуальные" лишена смысла, ИМХО.

B>Я что-то упустил?

Фразу, что я комментировал можно понимать как отключение виртуального механизма вызова и переход на статический при вызове виртуального метода из конструктора/деструктора. Такая мысль звучала явно, например, в параллельном обсуждении:

http://www.rsdn.ru/Forum/Message.aspx?mid=1712284&only=1
Автор: Константин Ленин
Дата: 03.03.06


Да, собственно, "в общем случае механизмы вызова виртуальной и "обычной" функции" отличаются и не только в конструкторе/деструкторе. Виртуальный вызов — он косвенный, что позволяет реализовать полиморфизм.
А вот и пример для вызова в конструкторе:

#include <stdio.h>

struct A {
  void sf() { ::printf("A::sf\n"); }
  virtual void vf() { ::printf("A::vf\n"); }
};

struct B : A {
  void sf() { ::printf("B::sf\n"); }
  virtual void vf() { ::printf("B::vf\n"); }
};

struct C : B {
  C() {
    ((A *) this)->sf();
    ((A *) this)->vf();
  }
};

void main() { C c; }


Лазар
Re[4]: Почему не вызывается перегруженная виртуальная функци
От: Лазар Бешкенадзе СССР  
Дата: 15.03.06 08:55
Оценка:
Здравствуйте, ekamaloff, Вы писали:

E>Здравствуйте, Лазар Бешкенадзе, Вы писали:


ЛБ>>На самом деле работают, более того, работают именно как

ЛБ>>виртуальные, просто вызывается последний override на момент
ЛБ>>создания объекта класса исполняемого конструктора/деструктора.

E>Для интереса посмотрел, что делает MS VC++ 7.1:


[snip] интересное исследование asm

E>Как видно из этого листинга, ни о каком виртуальном вызове (в случае с Foo), тут речи и не идет. Адрес берется не из vtable, а уже известен на момент компиляции. Поэтому фраза "работают именно как виртуальные", как справедливо заметил Bell, совершенно несправедлива.


А мы когда-нибудь слышали об оптимизации?

E>Кстати если Foo сделать в классе Base чисто виртуальной, то получим ошибки линковки:


E>

Win32Console error LNK2019: unresolved external symbol "public: virtual void __thiscall Base::Foo(void)" (?Foo@Base@@UAEXXZ) referenced in function "public: virtual __thiscall Base::~Base(void)" (??1Base@@UAE@XZ)


E>Если при этом убрать вызов Foo из деструктора Base, но оставить этот же вызов в main(), то ошибки нет.


Я уже писал, что если чисто виртуальную определить и вызвать напрямую, то может показаться, что работает.
Отсюда не следует, что поведение перестало быть undefined.

E>В-общем все это ИМХО говорит о том, что ни о какой виртуальности вызовов в конструкторе/деструкторе и речи не идет.


Вглядись в пример, что я привел в ответ на реплику Bell.

E>ЗЫ: насколько это согласуется со стандартом, не знаю.


HTH
Лазар
Re[5]: Почему не вызывается перегруженная виртуальная функци
От: ekamaloff Великобритания  
Дата: 15.03.06 09:02
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>А мы когда-нибудь слышали об оптимизации?


Не думаю, что в данном случае имело место оптимизация

ЛБ>Вглядись в пример, что я привел в ответ на реплику Bell.


Ответ на реплику Bell совершенно не согласуется с вашей изначальной фразой:

более того, работают именно как
виртуальные, просто вызывается последний override на момент
создания объекта класса исполняемого конструктора/деструктора


Хотелось бы понять, что вы хотели этим сказать.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[3]: Почему не вызывается перегруженная виртуальная функци
От: functional Украина http://www.compuniver.iatp.org.ua/
Дата: 15.03.06 09:40
Оценка:
Здравствуйте, Symon, Вы писали:
[...]
S>Хорошо хоть не читайте Страуструпа

У Страуструпа это, кстати, трудно найти (я не нашел ) — но наверняка оно там есть

Проще разбираться по более поздней, "компилятивной" литературе. Например "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices" (Herb Sutter, Andrei Alexandrescu).

Там Item 49 так и называется: Avoid calling virtual functions in constructors and destructors.

Прямо говорится:

...any direct or indirect call to an unimplemented pure virtual function from a constructor or destructor results in undefined behavior.


Успехов
Re[6]: Почему не вызывается перегруженная виртуальная функци
От: Лазар Бешкенадзе СССР  
Дата: 15.03.06 09:53
Оценка:
Здравствуйте, ekamaloff, Вы писали:

E>Здравствуйте, Лазар Бешкенадзе, Вы писали:


ЛБ>>А мы когда-нибудь слышали об оптимизации?


E>Не думаю, что в данном случае имело место оптимизация


ЛБ>>Вглядись в пример, что я привел в ответ на реплику Bell.


E>Ответ на реплику Bell совершенно не согласуется с вашей изначальной фразой:


E>

E>более того, работают именно как
E>виртуальные, просто вызывается последний override на момент
E>создания объекта класса исполняемого конструктора/деструктора


E>Хотелось бы понять, что вы хотели этим сказать.


По-моему здесь все ясно. Виртуальные вызовы виртуальных функций
делаются виртуально везде, в том числе и в ctor/dtor.
Это и демонстрирует пример.
Демонстрация оптимизированного компилятором кода в твоем сообщении
ничего не доказывает. Могу только привести еще один пример:

#include <stdio.h>

struct A {
  virtual void v() { ::printf("A::v\n"); }
};

struct B : A {
  B() { f(); }
  void f() { v(); }
};

struct C : B {
  virtual void v() { ::printf("C::v\n"); }
};

void main() { C().f(); }


Куда яснее?
Лазар
Re[5]: Почему не вызывается перегруженная виртуальная функци
От: Bell Россия  
Дата: 15.03.06 10:26
Оценка: 1 (1) +1 -1
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>На самом деле работают, более того, работают именно как виртуальные


ЛБ>Фразу, что я комментировал можно понимать как отключение виртуального механизма вызова и переход на статический при вызове виртуального метода из конструктора/деструктора.


Я, быть может, чего-то не понимаю, но для меня эти реплики не эквивалентны.

Что же касается моего утверждения о механизмах вызова, то тут я погорячился.
Стандарт требует, чтобы вызов (как приямой, так и опосредованный) виртуальной функции в конструкторе/деструкторе соответствовал прямому вызову "обычной" функции. Это просто сделать, когда имеет место прямой вызов (косвенно это подтверждает приведенный пример
Автор: ekamaloff
Дата: 15.03.06
), но в случае опосредованного вызова (как в твоем примере) все не так просто, и компилятору приходится идти на некоторые ухищрения, чтобы выполнить требования стандарта. Один из вариантов реализации этих самых ухищрений — использовать готовый механизм виртуального вызова через таблицу виртуальных функций. Единственное, что нужно сделать для обеспечения требуемого поведения — последовательно подменять указатель на таблицу виртуальных функций по мере создания/разрушения объекта. Однако в общем случае может быть использован другой механизм, и поэтому говорить о том, что вызов именно "виртуальный" — некорректно.
Любите книгу — источник знаний (с) М.Горький
Re[7]: Почему не вызывается перегруженная виртуальная функци
От: ekamaloff Великобритания  
Дата: 15.03.06 10:29
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

[...]

Приведенный пример не в тему. Речь шла о непосредственном вызове виртуальной функции из конструктора/деструктора, а не о косвенном (через другую функцию). И повторюсь, что оптимизация тут ни при чем, она была отключена в приведенном мной примере.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[4]: Почему не вызывается перегруженная виртуальная функци
От: Кодт Россия  
Дата: 15.03.06 11:23
Оценка:
Здравствуйте, functional, Вы писали:

F>Прямо говорится:

F>...any direct or indirect call to an unimplemented pure virtual function from a constructor or destructor results in undefined behavior.

Прямой вызов виртуальной функции из ктора/дтора — статический. Если функция не определена (неважно, =0 она или нет), то получим не UB, а ошибку линковки.
Перекуём баги на фичи!
Re[8]: Почему не вызывается перегруженная виртуальная функци
От: Лазар Бешкенадзе СССР  
Дата: 15.03.06 11:58
Оценка: 6 (1)
Здравствуйте, ekamaloff, Вы писали:

E>Здравствуйте, Лазар Бешкенадзе, Вы писали:


E>[...]


E>Приведенный пример не в тему.




E>Речь шла о непосредственном вызове виртуальной функции из конструктора/деструктора, а не о косвенном (через другую функцию).


Ни в исходном сообщении, ни в сообщении на которое отвечал я, ни в стандарте
ничего не говорится о непосредственности.

E> И повторюсь, что оптимизация тут ни при чем, она была отключена в приведенном мной примере.


Отсюда не следует, что не делаются никакие оптимизации.

Проснись и пойми, что из того, что вызов сделан не через vtbl, не следует, что он невиртуальный.
Вот тебе пример непосредственного вызова:


#include <stdio.h>

struct A {
  void sf() { ::printf("A::s\n"); }
  virtual void vf() { ::printf("A::v\n"); }
};

struct B : A {
  void sf() { ::printf("B::s\n"); }
  virtual void vf() { ::printf("B::v\n"); }
};

struct C : B {
  using A::sf;
  using A::vf;
  C() {
    sf();
    vf();
  }
};

void main() { C(); }
Re[6]: Почему не вызывается перегруженная виртуальная функци
От: Лазар Бешкенадзе СССР  
Дата: 15.03.06 12:20
Оценка:
Здравствуйте, Bell, Вы писали:

B>Здравствуйте, Лазар Бешкенадзе, Вы писали:


ЛБ>>На самом деле работают, более того, работают именно как виртуальные


ЛБ>>Фразу, что я комментировал можно понимать как отключение виртуального механизма вызова и переход на статический при вызове виртуального метода из конструктора/деструктора.


B>Я, быть может, чего-то не понимаю, но для меня эти реплики не эквивалентны.


B>Что же касается моего утверждения о механизмах вызова, то тут я погорячился.

B>Стандарт требует, чтобы вызов (как приямой, так и опосредованный) виртуальной функции в конструкторе/деструкторе соответствовал прямому вызову "обычной" функции.

Вот чего требует стандарт:

12.7 (3)
... When a virtual function is called directly or indirectly from a constructor (including
from the mem-initializer for a data-member) or from a destructor, and the object to which
a call applies is the object under construction or destruction, the function called is the
one defined in the constructor or destructor's own class or in one of its bases, ...

Покажи, где тут говорится о каком-либо соответствии.
К тому же приведенный здесь пример
Автор: Лазар Бешкенадзе
Дата: 15.03.06
явно показывал несоответствие.
А тут еще один пример
Автор: Лазар Бешкенадзе
Дата: 15.03.06
.

B>Это просто сделать, когда имеет место прямой вызов (косвенно это подтверждает приведенный пример
Автор: ekamaloff
Дата: 15.03.06
), но в случае опосредованного вызова (как в твоем примере) все не так просто, и компилятору приходится идти на некоторые ухищрения, чтобы выполнить требования стандарта. Один из вариантов реализации этих самых ухищрений — использовать готовый механизм виртуального вызова через таблицу виртуальных функций. Единственное, что нужно сделать для обеспечения требуемого поведения — последовательно подменять указатель на таблицу виртуальных функций по мере создания/разрушения объекта. Однако в общем случае может быть использован другой механизм, и поэтому говорить о том, что вызов именно "виртуальный" — некорректно.


Тут так все перевернуто с ног на голову, что мне остается только улыбнуться:


Лазар
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.