вопрос к знатокам C++

Landstreicher

Объясните пожалуйста, что должна выдавать такая программа:


#include <stdio.h>
class A {
public:
A { foo; }
virtual void foo { printf("Parent\n"); }
};
class B : public A {
public:
B : A { }
virtual void foo { printf("Child\n"); }
};
int main
{
B m_b;
return 0;
}


У меня выводит Parent, хотя мне почему-то упорно кажется что должно выводить Child.
Проверялось на gcc версий 2.95.3, 3.0.4, 3.2, 3.2.3, 3.3, 3.3.2 prerelease - везде одно и то же.
У кого-нибудь есть под рукой другой компилятор?

xsar

vc6 parent

krishtaf

Parent
могу даже обьяснить почему, если интересно.

Papazyan

Нехорошо в конструкторе виртульные функции вызывать. Да и смысла в этом нет абсолютно никакого - естественно должна вызываться родная функция.

ruler

Объясни.
Это из-за того, что функция виртуальная.
А если убрать virtual в конструкторе B, что-то изменится?

Papazyan

В конструкторе и деструкторе, даже если он виртуальный, функции класса всегда вызываются напрямую. Их виртуальность роли не играет. Объясняется это тем очевидным соображением, что класс-child в этот момент уже разрушен или еще не построен.

natali20

Это поведение сформулировано в стандарте языка, п. 12.7.3
Почему так - очевидно. Если не очевидно - почитай книжки, гугль, в конце-концов.

krishtaf

Первая истина:
конструкторы предка выполняются первыми.
Вторая истина:
конструктор не может быть виртуальным.
Поэтому выполняется сначала конструктор предка( т.е. А ) с побочным эффектом( A { foo; } а затем выполняется тело конструктора класса В( т.е. ничего не выполняется ).
Причем, на основании второй истины выполняется именно foo класса А.

ruler

п. 12.7.3
Достаточно было бы этого

krishtaf

Было бы интереснее, если бы была иерархия из 3-х классов

tokuchu

Чтобы в функции A вызвать виртуальную функцию B, виртуальность функции A не требуется.

krishtaf

прицепился все-таки
Я просто к тому что при вызове конструктора класса А о другой виртуальной функции foo ничего не известно, поэтому она и вызовется.
Но это не освобождает от незнания второй истины

xsar

а если то же самое проделать с деструкторами ?

krishtaf

проделай.

xsar

проделал
Parent

tokuchu

> при вызове конструктора класса А
> о другой виртуальной функции foo ничего не известно
Вообще говоря это всегда так происходит с виртуальными функциями.

krishtaf

ты не прав

rid2000

Выводиться "парент". И это понятно...
При вызове конструктора В вызывается контруктор А.
А при внутренних вызовах метода вызываются методы, которые находяться в том же классе, где и их вызвали...
Т.е. , происходит так:
B::B
A::A
A::foo

krishtaf


жаль Лориена, теперь каждый будет писать, что выводится Parent. Это уже не оригинально.
Пусть кто-нибудь напишет нечто другое

ppplva

Обрати внимание на слово virtual.

rid2000

Я же и говорю, что при внутренних вызовах вызывается ...
А при внешних, типа
B pB = new B;
A* pA = B;
Вызовы типа
pA->foo; - это внешний вызов...

ppplva

Первый раз слышу о "внутренних" и "внешних" вызовах, честно.

Chupa

Вот _как_ это работает:


....
_ZN1BC1Ev: ; B::B
.LFB12:
pushl %ebp
.LCFI3:
movl %esp, %ebp
.LCFI4:
subl $8, %esp
.LCFI5:
movl 8(%ebp %eax
movl %eax, (%esp)
call _ZN1AC2Ev ; call A::A
movl 8(%ebp %eax
movl $_ZTV1B+8, (%eax) ; fill VMT
leave
ret
....
_ZN1AC2Ev: ; A::A
.LFB13:
pushl %ebp
.LCFI6:
movl %esp, %ebp
.LCFI7:
subl $8, %esp
.LCFI8:
movl 8(%ebp %eax
movl $_ZTV1A+8, (%eax) ; fill VMT
movl 8(%ebp %eax
movl %eax, (%esp)
call _ZN1A3fooEv ; call A::foo
leave
ret
....

ppplva

В этом случае дело именно в конструкторе, конструктор объекта A вызывается всегда для объекта A. То, что он внутри объекта B - совпадение.

rid2000



class X
{
public :
virtual void g (int j) { cout << "X::g(" << j << ")\n"; }
void g { cout << "X::g" << '\n'; }
};
class Y : public X
{
public :
void g (int j) { cout << "Y:;g(" << j << ")\n"; }
void g { cout << "Y::g" << '\n'; }
};
void d (X * px, Y * py)
{
px->g (2);
px-> g ;
py -> g (2);
py-> g ;
}
void f
{
X x; Y y; X * px; &#211; * py;
px = &x; py = &y;
d (px, py) ;
px = py;
d(px, py) ;
d(py,py) ;
}


Тут скажи, что выведет...
При вызове f...
Это просто для саморазвитие

ppplva

1.cpp:4: error: stray '\' in program
1.cpp:4:59: missing terminating " character
1.cpp:9: error: base class `X' has incomplete type
1.cpp:11: error: stray '\' in program
1.cpp:11:51: missing terminating " character
1.cpp:25: error: parse error before `#' token
1.cpp:36: error: syntax error at end of input

ruler

Ничего не выведет.
Тут main'а нет

ppplva

Это был не с++ !

rid2000

Это я с ворда скопировал, там чуть ошибок поправить надо...
В ворде просто "<<" сделано как открывающая скобка...
Мейн сами напишите
void main
{
f;
}

Chupa

> Вот _как_ это работает:
В частности, из кода видно, что указатель на VMT выставляется после инициализации
parent класса и перед кодом конструктора.
Таким образом, внутри самого конструктора можно обращаться только к методам класса.
Child класс к этому моменту не проинициализирован, поэтому его методы вызывать было бы неправильно.

rid2000

Вот полный код


#include <iostream.h>
class X
{
public:
virtual void g (int j) { cout << "X::g(" << j << ")\n"; }
void g { cout << "X::g" << '\n'; }
};
class Y : public X
{
public:
void g (int j) { cout << "Y:;g(" << j << ")\n"; }
void g { cout << "Y::g" << '\n'; }
};
void d (X * px, Y * py)
{
px->g (2);
px-> g ;
py -> g (2);
py-> g ;
}
void f
{
X x; Y y; X * px; Y * py;
px = &x; py = &y;
d (px, py) ;
px = py;
d(px, py) ;
d(py,py) ;
}
void main
{
f;
}


Сперва подумайте. А потом свертесь с компилятором

ppplva

Сверился, совпало.

Landstreicher

> В конструкторе и деструкторе, даже если он виртуальный, функции класса всегда вызываются напрямую
Это объясняет только наполовину. Например, как быть в следующем примере:


#include <stdio.h>
class A {
public:
A { callFoo; }
void callFoo { foo; }
virtual void foo { printf("Parent\n"); }
};
class B : public A {
public:
B : A { }
virtual void foo { printf("Child\n"); }
};
int main
{
B b;
b.callFoo;
return 0;
}


Здесь в конструкторе вызывается метод callFoo который явно вызывает виртуальный метод:


_ZN1A7callFooEv:
[skipped]
movl (%edx %eax
call *%eax
leave
ret


То есть подобное поведение свидетельствует о том что, на время вызова конструктора базового класса в объект ставится таблица виртуальных методов базового класса, затем когда он уже предок инициализирова, объекту прописывает его "родная" таблица виртуальных методов.
В пункте 17.2.3 написано (цитирую Draft 2):
When a virtual function is called directly or indirectly from a constructor (including from
the mem-initializer for a data member) or from destructor, and the object to which is the call
applies is the object under construction or destruction, the function is called is the one defined
in the constructor or destructor's own class or in one of its bases, but not a function overriding
it in a class derived from the constructor or destructor's class, or overriding it in one of the
other base classes of the most derived object.
Насколько я понимаю, иного способа реализовать выделенное жирным шрифтом нет.
То есть при наличии вложенной цепочки конструкторов типа B::B : A C::C : B будет по очереди меняться таблица виртуальных методов на A, B, C?
Кстати, если вы тыкаете ссылками на стандарт, скажите где вы его взяли, а то я ничего кроме draft-ов найти не могу.

Landstreicher

Листинг объясняет многе.
> В частности, из кода видно, что указатель на VMT выставляется после инициализации
> parent класса и перед кодом конструктора.
я правильно понимаю, что подобная "переустановка" VMT положена по стандарту?

krishtaf

По поводу того, чтобы сделать функцию и там явно вызывать виртуальную функцию - это уже точно нужно смотреть в стандарт. Не удивлюсь, что разные реализации по разному смотрят на эти вещи.
Исошный стандарт С++ первой редакции за 98 год был в сети.

Landstreicher

внутренних и внешних вызовов не бывает, бывают только просто вызовы.
вызов виртуаьлного метода - это вызов функции по адресу, лежащему в VMT и все.

krishtaf

в Паскале бывают

Landstreicher

> в Паскале бывают
это как? приведи ассемблерный листинг внутреннего и внешнего вызова чтобы сравнить

krishtaf


или короткие и дальние
давно это было
на первом курсе
но то что вызов функции в одном сегменте и вызов функции из другого сегмента памяти в Паскале есть
и на АСМе тоже это можно написать

Но это все не относится к С++

Chupa

> я правильно понимаю, что подобная "переустановка" VMT положена по стандарту?
Стандарт может не знать подобных тонкостей реализации вообще.
Если _такая_ реализация позволяет добиться поведения,
указанного в стандарте (в частности, процитированный тобой кусок значит она правильная.

Landstreicher

> или короткие и дальние
это не то, я внутренние и внешние хочу

Chupa

>> или короткие и дальние
> это не то, я внутренние и внешние хочу
в дельфях можно взять указатель на метод класса
можешь считать вызов по такому указателю внешним
зы а ещё там виртуальные конструкторы есть

natali20

А у нас в квартире газ!
А у нас - водопровод, вот!

Landstreicher

вобщем, длительные поиски увенчались успехом, кому надо берите:
http://lorien.local/pub/docs/c++std/
Оставить комментарий
Имя или ник:
Комментарий: