Как объект C ++ хранит информацию о своих функциях-членах - PullRequest
3 голосов
/ 21 марта 2011
class A {
       public :
       void printSometext() {
       std::cout << "printing A" << std::endl;
       }
    };
class B {
       public : 
       void printSometext() {
       std::cout << "printing B" << std::endl;
       }
    };

int main() {
   A* a = new A();
   a->printSometext();
   return 0;
}

Как объект C ++ хранит информацию о своих функциях-членах.Давайте рассмотрим код выше.Когда я вызываю printSometext для объекта «a», откуда он знает, какую функцию вызывать и как находит правильный метод.При печати размера объекта он печатает размер суммирования его переменной-члена (+ allignments).Поэтому, пожалуйста, предоставьте некоторую внутреннюю информацию о том, как происходит вызов функции-члена.

Спасибо, Деймус

Ответы [ 4 ]

7 голосов
/ 21 марта 2011

Вы неправильно поняли основы программирования на C ++.a не знает о printSomeText во время выполнения, это компилятор и компоновщик, который переводит приведенный выше код в двоичный код, который выполняет эти задачи.Во время выполнения есть только несколько инструкций.

4 голосов
/ 21 марта 2011

Ну, это интересный вопрос, но позвольте мне попытаться ответить на него очень методично !!!

скажем, компилятор должен разрешить вызов следующим образом: *

a-> SomeFunc ();

*.

Теперь компилятор будет методично выполнять следующие шаги.

1.) Во-первых, компилятор знает объявленный тип переменной a, поэтому он проверит, есть ли у объявленного типа объекта a (lets call this, class A for time being), метод с именем someFunc () и что это должно быть публично. Этот метод может быть объявлен в class A, или он может быть производным от одного из базовых классов класса A, но это не имеет значения для компилятора, и он просто проверяет его существование с помощью спецификатора доступа существо public.

  • Нет необходимости говорить, что любая ошибка на этом шаге вызовет ошибку компилятора.

2.) Во-вторых, как только метод проверен на принадлежность к классу A, компилятор должен разрешить вызов правильного метода, поскольку многие методы могут быть там с одинаковыми именами (благодаря перегрузке функции). Этот процесс разрешения правильного метода называется overloading resolution. Компилятор достигает этого, сопоставляя сигнатуры вызываемого метода со всеми перегруженными методами, которые являются частью класса. Таким образом, из всех someFunc() s будет найден и рассмотрен только правильный someFunc () (соответствующий сигнатурам с вызываемым методом).

3.) Теперь наступает трудная часть. Может случиться так, что someFunc () может быть переопределен в одном из подклассов класса A (lets call this class AA and needless to say it is some subclass of A), и эта переменная a (объявлена ​​как тип) A) может фактически ссылаться на объект класса AA (это допустимо в C ++ для включения полиморфизма). Теперь, если объявлено, что метод someFunc () имеет тип virtual, в базовом классе (т.е. в классе A) и someFunc () был переопределен подклассом (ами) A (либо в AA, либо в классах между A и AA ), правильная версия someFunc () должна быть найдена компилятором.

  • Теперь представьте, что вы компилятор, и у вас есть задача выяснить, есть ли у класса AA этот метод. Очевидно, что класс AA будет иметь этот метод, поскольку он является подклассом A, и открытый доступ A в классе A уже был проверен на шаге 1 компилятором !!! , Но в качестве альтернативы, как упомянуто в предыдущем абзаце, someFunc () может быть переопределен классом AA (или любым другим классом между A и AA), что и нужно отлавливать компилятору. Следовательно, вы (поскольку вы играете в компиляторе) могли бы сделать систематическую проверку, чтобы найти самый нижний (самый низкий в дереве наследования) переопределенный метод someFunc (), начиная с класса A и заканчивая в классе AA. В этом поиске вы будете искать те же сигнатуры методов, которые были проверены при разрешении перегрузки. Этот метод будет методом, который будет вызван.

  • Теперь вы можете задаться вопросом: «Какого чёрта», этот поиск выполняется каждый раз? ... Ну не совсем. Компилятор знает, что каждый раз обнаруживает это, и поэтому поддерживает структуру данных с именем Virtual Table для каждого типа класса. Представьте себе виртуальную таблицу как отображение из сигнатур методов (которые являются общедоступными) в указатели функций. Эта виртуальная таблица создается компилятором во время процесса компиляции и сохраняется в памяти во время выполнения программы. В нашем примере класс A и класс AA будут иметь свои собственные виртуальные таблицы. И когда компилятор должен найти someFunc () в классе AA (поскольку фактический объект, на который указывает переменная a, имеет тип AA), он просто найдет указатель на функцию через виртуальную таблицу класса AA. Это так же просто, как хэширование в таблице и операция с постоянным временем.

Привет

Avid

3 голосов
/ 21 марта 2011

Добавление к ответу Аши (+1):

Если вы действительно хотите понять, как все работает внутри, я предлагаю изучить ассемблер и изучить сгенерированный машинный код. Вот что я сделал.

1 голос
/ 21 марта 2011

Компилятор знает типы переменных.Когда вы используете.или оператор ->, он ищет символ слева в контексте типа класса правого аргумента.Так что в вашем примере он находит только A :: printSomeText.

- Джеймс Канзе

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...