2013-07-11 3 views
3

Я уверен, что пропустил часть кода.Невозможно вызвать функцию производного класса в C++

У меня есть следующий код:

#include <iostream> 

using namespace std; 

class Record 
{ 
private: 
    int age; 
    string name; 
public: 
    virtual int getType()=0; 
}; 


class Student: public Record 
{ 
    private: 
    int level_; 
    public: 
    Student() 
    { 
     level_=1; 
    }; 
    ~Student() {}; 
    int getType() 
    { 
     return 1; 
    } 

    int level() 
    { 
     return level_; 
    } 
}; 


int main (int argc, char ** argv) 
{ 
    Record *r = new Student(); 
    cout <<"tuype " << r->getType(); 
    cout <<"Class " << r->level(); 
} 

Вопрос: Почему я не в состоянии вызвать r->level()?. Какие изменения необходимы для его вызова?

+3

Ваш класс 'Record' должен иметь виртуальный деструктор. Ваш класс 'Student' должен иметь неявно определенный. И ни одна из ваших функций не нуждается в запятой после тела. – chris

+1

Я думаю, что вообще не рекомендуется иметь метод 'getType()'. Это не всегда плохо, но старайтесь избегать его, если это возможно. И не забывайте о виртуальном деструкторе в классах, предназначенных для наследования, очень важно – SpongeBobFan

+0

@chris: +1 для совета виртуального деструктора, но не имеет значения, имеет ли 'Student' неявно определенный деструктор. Точки с запятой, * дрожь * ;-). –

ответ

8

Изменения к записи, чтобы сделать level() виртуальный

Вы писали:

Record *r = new Student(); 

После этой строки, компилятор считает r быть указателем либо Record или некоторые Record - (который он есть), но он знает только интерфейс, указанный для Record. Нет функции virtuallevel() в Record, поэтому вы не можете получить доступ к функции уровня Student через интерфейс Record. Просто добавьте такую ​​функцию Record и вы будете в порядке:

virtual int level() { return 0; } // Student may override implementation 

или

virtual int level() = 0; // Student MUST override implementation 

Альтернатива: проверка адресов является ли запись * студент

я говорю выше. ..

virtuallevel() в 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.

Рекомендуемые небольшие улучшения в Студента

Если вы сделаете levelvirtual в Record, а затем, чтобы сделать его более ясным для читателей Student класса, level() является реализация virtual функции от базового класса, вы можете использовать override ключевое слово, если у вас есть подходяще C++ 11-способный компилятор:

int level() override 
{ 
    return level_; 
} 

Это даст вам ошибка компилятора, если он не может найти соответствующий (не const) virtual int level() в базовом классе, поэтому он может избежать некоторых случайных проблем. Вы также можете повторить ключевое слово virtual, если вы считаете, что оно имеет ценность для документации (особенно хорошо для C++ 03, где override - это не вариант), но это не делает никакой функциональной разницы - функция остается virtual до тех пор, пока она (неявно или явно) переопределение виртуальной функции из базового класса.

+0

Хороший ответ, но я думаю, что автор хочет использовать 'Student * r = new Student()' здесь, потому что метод 'level()' специфичен для 'Student' и может быть бесполезным для других подклассов' Record' – SpongeBobFan

+0

@Tony, спасибо за объяснение, что означает, что производные функции класса не могут быть вызваны, если они не указаны в базовом классе в данном сценарии правильно? – Whoami

+0

@Whoami: до тех пор, пока вы обрабатываете объект как «запись», да. Я добавлю раздел, посвященный тому, как проверить, указывает ли 'Record *' на «Student», поэтому вы можете получить доступ к функции 'level()', даже если она не отображается через интерфейс базового класса. Этот метод считается хакерским, хотя ... лучше всего избегать, где это возможно, но также хакерски иметь так называемый «толстый» интерфейс, в котором базовый класс включает в себя виртуальные функции, для которых только подмножество производных классов имеет значимые импликации (реализация do-nothing может по-прежнему иметь смысл). Объясню. –

1

В документе отсутствует идентификатор уровня виртуального метода(). На стороне примечания, чтобы предотвратить утечку ресурсов, вы хотели бы определить виртуальный деструктор в записи.

class Record 
{ 
... 
public: 
    virtual ~Record() {} 
    virtual int level() = 0; 
    virtual int getType() = 0; 
}; 
2

Поскольку Record класс ничего не знает о функции под названием level()

Вам нужно сделать виртуальный level() функции в базовом классе.

2

Сделать виртуальную функцию в базовом классе как

virtual int level() = 0; 

После создания виртуальной функции level() в базовом классе записи, становится необходимым для студента, чтобы это функция level() в классе студентов.В настоящее время у вас нет виртуальной функции level() в классе Запись, из-за этого вы не можете получить доступ к функции класса level() класса учащихся, используя базовый класс Record, как вы делаете сейчас.

0

При компиляции, вы должны получить сообщение об ошибке:

error C2039: 'level' : is not a member of 'Record' 

Вы должны добавить это в вашем классе записи:

virtual int level()=0; 
3

Вы не можете позвонить r->level() потому что вы пытаетесь вызвать функция, которая не существует в записи класса. В ваших конкретных данных случаев указывали на r не только Record но Student в то же время, так что вы можете выбрать:

Student *r = new Student(); 
cout <<"tuype " << r->getType(); 
cout <<"Class " << r->level() 

или

Record *r = new Student(); 
cout <<"tuype " << r->getType(); 
cout <<"Class " << dynamic_cast<Student*>(r)->level() 

Если вы хотите, чтобы все Record s, чтобы иметь вы можете добавить чистую виртуальную функцию без реализации. Но если вы делаете, что вы будете не в состоянии создать instantate объекты класса Record (но вы можете instatntiate это дети классы):

class Record { ... 
    virtual int level() = 0; 
} 

Еще одна проблема: вы должны отметить свой деструктор в Record классе как виртуальные, так как при будет delete r конструктор Student не будет называться

+0

Для верхнего «можно выбрать либо:» код, вы имели в виду для 'Record * r = new Student();' to 'Student * r = new Student();'? Как это все еще сломан. +1 для определения потенциального использования 'dynamic_cast <>'. –

+0

@TonyD, да, я имел в виду именно это, спасибо за внимание. –

1

экземпляр студент upcasted для записи по:

Record *r = new Student(); 

Хотя держать экземпляр Student, г иМЕНИ как запись. Вызов функции r-> getType() привязан к ученику :: getType с маханизмом полиморфизма C++. Для вызова функции уровня(), вы можете:

 
1. Add a virtual function level() to Record class. 
2. downcasting r to Student class, as follows: 
Student *r_new = dynamic_cast<Student>(r); 
r_new->level(); 
Смежные вопросы