Изменения к записи, чтобы сделать level()
виртуальный
Вы писали:
Record *r = new Student();
После этой строки, компилятор считает r
быть указателем либо Record
или некоторые Record
- (который он есть), но он знает только интерфейс, указанный для Record
. Нет функции virtual
level()
в Record
, поэтому вы не можете получить доступ к функции уровня Student
через интерфейс Record
. Просто добавьте такую функцию Record
и вы будете в порядке:
virtual int level() { return 0; } // Student may override implementation
или
virtual int level() = 0; // Student MUST override implementation
Альтернатива: проверка адресов является ли запись * студент
я говорю выше. ..
virtual
level()
в Record
, поэтому вы не можете получить доступ к функции уровня Student
через интерфейс Record
.
... и показать, как вы можете добавить к интерфейсу Record
, но альтернатива, чтобы получить доступ к интерфейсу Student
снова, как в:
if (Student* p = dynamic_cast<Student*>(r))
std::cout << "Level " << p->level() << '\n';
Первые проверки линии является ли Record* r
бывает, указывая на Student
(конечно, в вашем коде он всегда есть, но представьте, что вы были внутри функции, которая принимала Record*
, или перебирали контейнер с такими указателями, где некоторые были действительно Student
s, а другие нет). Если это так, возвращаемый указатель может быть использован для доступа к нему как объект Student
, с любыми доступными функциональными возможностями/членами (и потенциально ограничениями, если некоторые функции Record
скрыты в некотором роде).
Такой подход, как правило, с неодобрением, как она возникает вопрос «почему мы лечения Student
как Record
если нам нужно знать„уровень“и другие Record
-derived типы даже не имеют понятия уровня? ».Тем не менее, такие вещи случаются иногда. Добавление функции level
к Record
не является идеальным, если Student
является (одним из) единственным производным классом, в котором он имел бы значимое значение: это то, что называется толстым интерфейсом - вы найдете несколько обсуждений их на языке программирования C++, если у вас есть копия.
(ответ sasha.sochka был первым упомянуть вариант dynamic_cast
- пожалуйста upvote)
Базового класс должен иметь виртуальный деструктор
Согласно комментарию Криса, вы должны добавить в Record
:
virtual ~Record() { }
Это обеспечивает реализацию деструктора производного класса, когда производный объект равен delete
d, указатель класса (например, если вы добавили delete r;
внизу main()
). (Компилятор по-прежнему будет гарантировать, что деструктор базового класса вызывается впоследствии).
Это неопределенное поведение, если вы этого не сделаете, и в лучшем случае вы обнаружите, что любые дополнительные члены данных, добавленные в производные классы, не имеют своих членов данных, называемых ... для int
, что безвредно, но, скажем, std::string
это может привести к утечке памяти или даже удерживать блокировку, чтобы впоследствии зависала программа. Конечно, не стоит полагаться на лучший случай Undefined Behavior ;-), но я думаю, что полезно понять, что определенно не происходит, если вы не сделаете базовый деструктор virtual
.
Рекомендуемые небольшие улучшения в Студента
Если вы сделаете level
virtual
в Record
, а затем, чтобы сделать его более ясным для читателей Student
класса, level()
является реализация virtual
функции от базового класса, вы можете использовать override
ключевое слово, если у вас есть подходяще C++ 11-способный компилятор:
int level() override
{
return level_;
}
Это даст вам ошибка компилятора, если он не может найти соответствующий (не const
) virtual int level()
в базовом классе, поэтому он может избежать некоторых случайных проблем. Вы также можете повторить ключевое слово virtual
, если вы считаете, что оно имеет ценность для документации (особенно хорошо для C++ 03, где override
- это не вариант), но это не делает никакой функциональной разницы - функция остается virtual
до тех пор, пока она (неявно или явно) переопределение виртуальной функции из базового класса.
Ваш класс 'Record' должен иметь виртуальный деструктор. Ваш класс 'Student' должен иметь неявно определенный. И ни одна из ваших функций не нуждается в запятой после тела. – chris
Я думаю, что вообще не рекомендуется иметь метод 'getType()'. Это не всегда плохо, но старайтесь избегать его, если это возможно. И не забывайте о виртуальном деструкторе в классах, предназначенных для наследования, очень важно – SpongeBobFan
@chris: +1 для совета виртуального деструктора, но не имеет значения, имеет ли 'Student' неявно определенный деструктор. Точки с запятой, * дрожь * ;-). –