Судя по вашему другому вопросу , кажется, вы не понимаете, как работают классы. Классы - это набор функций, которые работают с данными.
Сами функции не содержат памяти в классе. Следующий класс:
struct dumb_class
{
void foo(){}
void bar(){}
void baz(){}
// .. for all eternity
int i;
};
Имеет размер int
. Независимо от того, сколько у вас функций, этот класс будет занимать только пространство, необходимое для работы на int
. Когда вы вызываете функцию в этом классе, компилятор передаст вам указатель на место, где хранятся данные в классе; это указатель this
.
Итак, функция где-то находится в памяти, загружается один раз в начале вашей программы и ожидает вызова с данными для обработки.
Виртуальные функции разные. Стандарт C ++ не предписывает, как должно действовать поведение виртуальных функций, только то, каким должно быть это поведение. Как правило, реализации используют так называемую виртуальную таблицу или сокращенно vtable. Vtable - это таблица указателей на функции, которые, как и обычные функции, выделяются только один раз.
Возьмем этот класс и предположим, что наш разработчик использует vtables:
struct base { virtual void foo(void); };
struct derived { virtual void foo(void); };
Компилятору потребуется создать две vtables, одну для базы и одну для производной. Они будут выглядеть примерно так:
typedef /* some generic function pointer type */ func_ptr;
func_ptr __baseTable[] = {&base::foo};
func_ptr __derivedTable[] = {&derived::foo};
Как он использует эту таблицу? Когда вы создаете экземпляр класса выше, компилятор вставляет скрытый указатель, который будет указывать на правильный vtable. Поэтому, когда вы говорите:
derived d;
base* b = &d;
b->foo();
После выполнения последней строки она переходит к правильной таблице (__derivedTable
в этом случае), переходит к правильному индексу (0 в данном случае) и вызывает эту функцию. Как вы можете видеть, это в итоге вызовет derived::foo
, что и должно произойти.
Обратите внимание, что позже это то же самое, что и derived::foo(b)
, передавая b
как указатель this
.
Итак, когда присутствуют виртуальные методы, класс размера будет увеличиваться на один указатель (указатель на vtable.) Множественное наследование немного меняет это, но в основном это то же самое. Вы можете получить более подробную информацию на C ++ - FAQ .
Теперь к вашему вопросу. У меня есть:
struct base { virtual void foo(void) = 0; }; // notice the = 0
struct derived { virtual void foo(void); };
и base::foo
не имеют реализации. Это делает base::foo
чистой абстрактной функцией. Итак, если бы я назвал это, как указано выше:
derived d;
base* b = &d;
base::foo(b);
Какое поведение мы должны ожидать? Будучи чисто виртуальным методом, base::foo
даже не существует. Приведенный выше код является неопределенным поведением и может делать что угодно, от ничего до сбоя, с чем-то промежуточным. (Или хуже.)
Подумайте, что представляет собой чистая абстрактная функция. Помните, функции не принимают данных, они только описывают, как манипулировать данными. Чистая абстрактная функция говорит: «Я хочу вызвать этот метод и манипулировать моими данными. Как это сделать, решать только вам».
Поэтому, когда вы говорите: «Хорошо, давайте назовем абстрактный метод», вы отвечаете на вышеизложенное: «До меня? Нет, вы делаете это». на что он ответит "@ # ^ @ # ^". Просто не имеет смысла говорить кому-то, кто говорит «делай это», «нет».
Чтобы ответить на ваш вопрос напрямую:
"почему мы не можем создать объект для абстрактного класса?"
Надеюсь, вы видите, что абстрактные классы определяют только те функции, которые должен выполнять конкретный класс. Сам абстрактный класс - только предварительный план; Вы живете не в светокопиях, а в домах, в которых реализуются светокопии.