Функция, объявленная как Virtual в производном классе, не выполняется, вместо этого выдает ошибку компиляции. Почему?

Я понимаю виртуальную функцию и vTable, поэтому, пожалуйста, извините, если это тривиальный вопрос....

Основываясь на концепции vTable и vPtr, объясненной людьми, я понял и ожидаю, что нижеприведенная программа должна работать, но выдает ошибку.

Постановка задачи. В приведенном ниже примере, согласно пониманию, класс B должен иметь "vTable", содержащую функции f2() и f3(). Не так ли? (поскольку f2() унаследован от класса A, но переопределен, а f3 сделан виртуальным в самом классе B)

но при вызове функции " obj->f3(); " он выдает ошибку, как показано ниже, мне все еще интересно, почему это так?

ОШИБКА: в функции «int main()»: 31:10: ошибка: «класс A» не имеет члена с именем «f3»

// Example program
#include <iostream>
#include <string>

using namespace std;     
class A
{
    public:
    void f1() {cout<<"A::f1"<<endl;}
    virtual void f2() {cout<<"A::f2"<<endl;}
};    

class B: public A
{
    public:
    void f1() {cout<<"B::f1"<<endl;}
    void f2() {cout<<"B::f2"<<endl;}
    virtual void f3() {cout<<"B::f3"<<endl;}
};


int main()
{
    A* obj = new B();
    obj->f1();      // Early Binding (EB)
    obj->f2();      // Late Binding (LB)
    obj->f3();      // Error (though was expecting LB since f3 is virtual)
}

ОШИБКА : В функции 'int main()': 31:10: ошибка: 'класс A' не имеет члена с именем 'f3'


person Prince Rambade    schedule 25.11.2018    source источник
comment
f3() не является членом A. Конец.   -  person Sam Varshavchik    schedule 25.11.2018
comment
Где в vtable of A будет храниться f3?   -  person stark    schedule 25.11.2018
comment
Вы не можете добавлять новые виртуальные члены в базовый класс в производных классах.   -  person Jesper Juhl    schedule 25.11.2018
comment
@JesperJuhl, спасибо за ответ. Вероятно, у меня может быть некоторое неправильное представление о понимании vTable, поэтому, пожалуйста, поправьте меня, если я ошибаюсь. Я изучил, что во время выполнения тип объекта, на который указывает указатель A, оценивается, и vTable этого класса, то есть класса B, выбирается для определения вызываемой функции f3(). Поскольку класс B vTable должен иметь B::f3() в vTable, я предполагал, что компилятор вызовет его, но это выдало ошибку, которая меня смутила.   -  person Prince Rambade    schedule 25.11.2018
comment
Изучение арифметики с плавающей запятой (fp) должно выполняться с правильным пониманием представления типов fp и пределов точности, подразумеваемых этим представлением. Представление типов fp нельзя абстрагировать. Вы просто не можете пропустить это. Изучение использования виртуальных функций не подразумевает понимания деталей vtables (vtables используются 100% компиляторов C++), потому что семантика виртуальных функций лучше всего описывается в терминах абстрактной семантики как функции типа объекты. Вам также необходимо понимать типы выражений (синтаксические конструкции).   -  person curiousguy    schedule 26.11.2018


Ответы (2)


Детали реализации (vtable или нет) не имеют значения. В глазах стандарта (и, следовательно, компилятора) ваш код недействителен, потому что A не имеет метода f3(), а obj имеет тип A*, вот и все.

person Hatted Rooster    schedule 25.11.2018
comment
Ваш ответ мне не так ясен. Я пытаюсь понять концепцию позднего связывания в этом контексте. Пожалуйста, поправьте меня, если я неправильно понимаю, и помогите мне понять это правильно. Если вы имеете в виду, что во время компиляции компилятор сначала проверяет тип «obj», который в данном случае является A*, и сначала пытается найти метод f3(). если он находит конкретную функцию f3(), то компилятор сам свяжет ее там, иначе, если f3() объявлен как виртуальный в классе A, то компилятор откладывает привязку во время выполнения? но поскольку в классе нет f3(). это не удалось во время компиляции. - person Prince Rambade; 25.11.2018
comment
@PrinceRambade Имена привязываются во время компиляции. Всегда. - person curiousguy; 26.11.2018

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

Как и в C, в C++ имена просматриваются и связываются во время компиляции: любое используемое имя должно ссылаться на видимое объявление. Единственные имена, которые могут быть найдены компилятором, — это имена в классе, обозначенном выражением. (Выражения — это синтаксические конструкции, они существуют на во время компиляции.)

В статически типизированном языке типы выражений являются статическими и вычисляются внутри каждой функции в соответствии с объявлениями.

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

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

Позднее связывание не является проблемой здесь, в вашем неправильно сформированном коде, который даже не компилируется, не запускается и не вызывает какую-либо функцию во время выполнения для любого полиморфного объекта:

obj->f3();      // Error (though was expecting LB since f3 is virtual)

Когда obj имеет тип указателя, obj->something совпадает с (*obj).something; а obj — локальная переменная, определенная как

A* obj = (...something not relevant for the argument...);

поэтому obj имеет тип A*, а *obj имеет тип A, который является классом, определенным как:

class A
{
    public:
    void f1() {cout<<"A::f1"<<endl;}
    virtual void f2() {cout<<"A::f2"<<endl;}
};

В классе, обозначенном выражением *obj, нет объявления какого-либо члена f3, видимого в классе. Поэтому вызов сформирован неправильно. Он недействителен во время компиляции, и компилятор отклоняет его.

И это полный анализ этой неправильно построенной строки. Остальное не имеет значения для статически типизированного языка. Производные классы, не названные в соответствующем коде (который является просто объявленным типом obj и выражением, определяющим, где выполняется поиск имени, *obj), не имеют значения. Это сущность статической типизации.

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

person curiousguy    schedule 26.11.2018