Возможный дубликат:
В чем проблема нарезки в C ++?
У меня есть простой код в качестве примера полиморфизма и наследования
class A
{
public:
int fieldInA;
void virtual overloadedFunc()
{
printf("You are in A\n");
}
};
class B : public A
{
public:
int fieldInB;
void overloadedFunc()
{
printf("You are in B\n");
}
};
void DoSmth(A* a)
{
a->overloadedFunc();
}
void DoSmthElse(A a)
{
a.overloadedFunc();
}
int _tmain(int argc, _TCHAR* argv[])
{
B *b1 = new B();
B b2;
//Breakpoint here
DoSmth(b1);
DoSmthElse(b2);
scanf("%*s");
return 0;
}
Когда я останавливаюсь в точке останова, значения _vfptr [0] для b1 и _vfptr [0] для b2 совпадают (SomeAddr (B :: overloadedFunc (void))).
После передачи b1 в качестве параметра в DoSmth (), _vfptr [0] локальной переменной a все еще является someAddr (B :: overloadedFunc (void)), но _vfptr [0] a в DoSmthElse теперь является someAddr (A :: overloadedFunc (void) )). Я уверен, что это мое неправильное понимание концепции перегрузки функций, но я не мог понять, почему в первом случае я увидел «Вы в B», а во втором — «Вы в A». То же самое с A * b1 = new B (); DoSmth (b1); // Вы в B, почему?
Во-первых, вы должны понять свою терминологию правильно! Ты не сделал перегрузка любые функции, вы отменяют их:
virtual
), и вы заменяете вызываемую функцию функцией, применимой к объекту более специализированного класса. Вы переопределение первоначальный смысл. Выбор правильного переопределения является операцией во время выполнения, в C ++ используется нечто похожее на таблицу виртуальных функций.Термины достаточно запутанные, и, что еще хуже, они даже взаимодействуют: во время компиляции выбирается правильная перегрузка, которая в итоге вызывает виртуальную функцию, которая, таким образом, может быть переопределена. Кроме того, переопределения производного класса могут скрывать перегрузки, иначе унаследованные от базового класса. Все это может не иметь никакого смысла, если вы не можете разобраться в терминах, хотя!
Теперь, для вашей актуальной проблемы, когда вы звоните DoSmthElse()
Вы передаете свой объект b2
по значению для функции, принимающей объект типа A
, Это создает объект типа A
скопировав A
подобъект вашего B
объект. Но с тех пор B
происходит от A
не все B
будет представлен, то есть A
объект, который вы видите в DoSmthElse()
не ведет себя как B
объект, но как A
объект. В конце концов, это является A
объект, а не B
! Этот процесс обычно называется нарезка: вы отсекаете части B
объект, который сделал его особенным.
Чтобы получить полиморфное поведение, вам нужно вызывать виртуальные функции по указателю или по ссылке на базовый класс, а не на экземпляр базового класса. Когда вы вызываете эту функцию
void DoSmthElse(A a)
Вы прошли экземпляр B
, Это передача по значению, и поэтому аргумент нарезанный копия вашего B
экземпляр, который вы передаете ему. По сути это означает, что все свойства B
которые являются общими для A
сохранены в этом экземпляре и все свойства B
специфичные для B
и не A
потеряны Из-за этого объект внутри DoSmthElse()
что функция overloadedFunc()
называется в настоящее время исключительно типа A
(и больше не типа B
) и так конечно A::overloadedFunc()
называется.
В первом случае с DoSmth
когда аргумент имеет указатель типа на базовый класс, полиморфное поведение получается, как и следовало ожидать — B*
аргумент передается функции и создается копия этого указателя, которая теперь имеет тип A*
, Хотя копия была приведена к A*
указанный объект все еще имеет тип B
, Потому что указанный объект имеет тип B
тот факт, что доступ к нему осуществляется через указатель на его базовый класс, гарантирует, что функция B::overloadedFunc()
на самом деле называется, когда линия
a->overloadedFunc();
выполняется, потому что виртуальная функция overloadFunc()
переопределяется классом B
. Был класс B
не реализована отдельная версия виртуальной функции базового класса (т.е. B
переопределяет класс A
функциональности), то вместо этого была бы вызвана версия базового класса.