наследование — скрытие, перегрузка или перезапись в переполнении стека

Тогда у меня снова вопрос. что-то вроде этого:

#include <iostream>
using namespace std;

class Base
{
public:
void foo()
{
cout<<"Base."<<endl;
}
};

class Derive:public Base
{
public:
void foo()
{
cout<<"Derive."<<endl;
}
};

int main()
{
Derive d;
Base *pb=&d;    //attention here
pb->foo();      //ateention here
system("pause");
return 0;
}

И вывод «База». Тогда правила функций не работают, я запутался в этом, не могли бы вы помочь мне? Благодарю.

1

Решение

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

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

#include <iostream>

struct base {
virtual void foo() {
std::cout << "base::foo";
}
};

struct derived : base {
virtual void foo() const {
std::cout << "derived::foo";
}
};

int main(){
base *b = new derived;

b->foo();
}

Вот foo квалифицируется как virtual как в базовых, так и (казалось бы, избыточно) производных классах, но вызов b->foo() еще распечатывает base::foo,

const добавлено в подпись derived::foo означает, что он больше не соответствует подписи base::fooпоэтому вместо переопределения виртуальной функции мы по-прежнему получаем две отдельные функции с одинаковым именем, поэтому derived::foo шкуры base::foo, но не отменяет это. Несмотря на virtual квалификация, мы получаем статическое связывание, так b->foo(); вызывает базовую функцию, а не производную, даже если b указывает на объект derived тип.

Как отметил Тони Д. в комментарии, C ++ 11 добавил новую складку в язык, чтобы предотвратить это. Если вы хотите переопределить функцию базового класса, вы можете добавить идентификатор override к функции в производном классе:

struct derived : base {
virtual void foo() const override {
std::cout << "derived::foo";
}
};

При этом, если есть разница в сигнатуре функции (как в случаях, показанных здесь), компилятор выдаст сообщение об ошибке, предупреждающее вас о том, что derived::foo помечен как override, но на самом деле не переопределяет функцию из базового класса. Однако это было добавлено в C ++ 11, поэтому, если вы используете более старый компилятор, эта функция может быть не реализована (хотя, к счастью, компиляторы, которые ее не реализуют, быстро уходят в небытие).

Исправление подписи в базовом классе:

virtual void foo() const // ...

…позволит скомпилировать код и дать правильные результаты.

4

Другие решения

функция Base::foo не является виртуальным. Он вызывается потому, что используется указатель на базовый класс.

Если вы измените код следующим образом:

class Base
{
public:
virtual void foo() // add virtual
{
cout<<"Base."<<endl;
}
};

вывод должен бытьПроизводный».

1

Вы видите это поведение, потому что foo() не объявлен как virtual в Base, В C ++ функции-члены не являются виртуальными по умолчанию. Вы должны явно объявить функцию как virtual для того, чтобы воспользоваться динамической диспетчеризацией и полиморфизмом.

1

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

Пример кода 1:

struct Base
{
void foo()
{
}
};

struct Derive: public Base
{
void foo()
{
}
};

int main()
{
Derive d;
Base *pb=&d;
d.foo();    // Resolves to Derived::foo()
pb->foo();  // Resolves to Base::foo()
return 0;
}

Почему d.foo() вызов Derived::foo() а также pb->foo() вызов Base::foo()?

Ответ на этот вопрос заключается в шагах, которые компилятор предпринимает для разрешения этих привязок функций.

Дан объект типа T и имя функции f, компилятор ищет функции с именем f в T, Если он находит только одну функцию с именем fпоиск функций на этом заканчивается. Если он находит более одной функции, он пытается разрешить перегрузку из набора функций, найденных в T,

Если он не находит никаких функций с именем f в T а также T имеет базовый класс, он пытается вышеуказанную логику в Tбазовый класс. Если T не имеет базовых классов, компилятор сообщает об ошибке.

Подойдя к объектам примера кода …

При обработке вызова функции d.foo()компилятор ищет foo в Derived, Он находит там одно совпадение и останавливается. Так как Derived::foo() это не virtual функция, привязка выполняется во время компиляции. Во время выполнения, Derived::foo() называется.

При обработке вызова функции pb->foo()компилятор ищет foo в Base, Он находит там одно совпадение и останавливается. Так как Base::foo() это не virtual функция, привязка выполняется во время компиляции. Во время выполнения, Base::foo() называется.

Пример кода 2:

struct Base
{
void foo(int i)
{
}
};

struct Derive: public Base
{
void foo()
{
}
};

int main()
{
Derive d;
Base *pb=&d;
d.foo();      // Resolves to Derived::foo()
d.foo(10);    // Compiler error.

pb->foo(10);  // Resolves to Base::foo(int)
pb->foo();    // Compiler error.
return 0;
}

Почему компилятор выдает здесь ошибки?

При обработке вызова функции d.foo(10)компилятор ищет foo в Derived, Он находит там одно совпадение и останавливается. Он пытается использовать эту функцию, но подпись функции не соответствует вызывающему коду. Следовательно, это ошибка компилятора.

При обработке вызова функции pb->foo()компилятор ищет foo в Base, Он находит там одно совпадение и останавливается. Он пытается использовать эту функцию, но подпись функции не соответствует вызывающему коду. Следовательно, это ошибка компилятора.

Как только компилятор найдет foo в Derived он не идет в поисках соответствия foo в Base,

В этом случае вы можете думать о Derived::foo быть полностью скрытым Base::foo,

Пример кода 3:

struct Base
{
void foo()
{
}
};

struct Derive: public Base
{
void foo()
{
}
void foo(int )
{
}
};

int main()
{
Derive d;
d.foo();      // Resolves to Derived::foo()
d.foo(10);    // Resolves to Derived::foo(int)

Base *pb=&d;
pb->foo();    // Resolves to Base::foo()
pb->foo(10);  // Compiler error.

return 0;
}

При обработке вызовов функции d.foo() а также d.foo(10)компилятор ищет foo в Derived, Он находит там пару совпадений и останавливается. Затем он пытается разрешить перегрузку. Он может найти соответствие для обеих версий. Так как ни один из Derived::foo()с является virtual функция, привязка выполняется во время компиляции.

При обработке вызовов функции bp->foo() а также bp->foo(10)компилятор ищет foo в Base, Он находит там пару совпадений и останавливается. Затем он пытается разрешить перегрузку. Он может найти соответствие для первых версий, но не для второй версии. Это генерирует ошибку для второго вызова.

Вот Derived::foo не только спрятать Base::foo но есть и две перегруженные версии Derived::foo,

Пример кода 4:

struct Base
{
virtual void foo()
{
}
void foo(int)
{
}
};

struct Derive: public Base
{
void foo()
{
}
void foo(int )
{
}
};

int main()
{
Derive d;
d.foo();      // Resolves to Derived::foo()
// But Derived:foo() gets called at run time.

d.foo(10);    // Resolves to Derived::foo(int)
// But Derived:foo(int) gets called at run time.

Base *pb=&d;
pb->foo();    // Resolves to Base::foo()
// But Derived:foo() gets called at run time due to
// function overwritting.

pb->foo(10);  // Resolves to Base::foo(10)
// Base:foo(int) gets called at run time.

return 0;
}

Это включает в себя скрытие функций, перегрузку функций и перезапись функций.

Derived::foo шкуры Base::foo,
Derived::foo() а также Derived::foo(int) перегружены.
Base::foo() а также Base::foo(int) перегружены.
Base::foo() перезаписывается Derived::foo(),

Я надеюсь, что это прояснит некоторые ваши сомнения.

1

В этом коде нет перегрузки или перезаписи. Base::foo называется, а не Derive::foo потому что не было никакой спецификации программистом, чтобы использовать динамическое связывание для имени foo, Если виртуальный спецификатор не предоставлено, компилятор находит функцию на основе статического типа объекта, на который он вызывается, а не на тип, на который он может указывать. Это называется статическим связыванием и выполняется во время компиляции.

Если используется виртуальный спецификатор, имя функции ищется во время выполнения и вызывается на основе типа времени выполнения объекта.


Кроме того, вашему базовому классу нужен виртуальный деструктор по той же причине, что и выше. Если у тебя есть Base указатель класса, который указывает на Derive удаление этого указателя вызовет только деструктор базового класса, а не базовый и производный.

0
По вопросам рекламы [email protected]