проблема с виртуальными функциями
Столкнулся с небольшой проблемой, касательно виртуальных функций.
Стыдно спрашивать, ибо это нечто элементарное.
Просто я уже успел подзабыть основы ООП в C++.
Вот пример.
class A
{
public:
void Func()
{
VirtFunc();
}
~A()
{
Destroy();
}
virtual void VirtFunc()
{
puts("A::VirtFunc() called");
}
virtual void Destroy()
{
puts("A::Destroy() called");
}
};
class B : public A
{
public:
virtual void VirtFunc()
{
puts("B::VirtFunc() called");
}
virtual void Destroy()
{
puts("B::Destroy() called");
}
};
int main()
{
{
B BObject;
BObject.Func();
}
getchar();
return 0;
}
В методе Func вызывается виртуальная функция VirtFunc из наследника - так и надо.
Но в деструкторе наследника почему-то вызывается виртуальная функция Destroy из родителя.
Мне нужно при разрушении наследника вызвать Destroy наследника.
Где я туплю?
Класс B наследует деструктор класса A.
Но в нем, по идее, должен вызываться метод B::Destroy(), т.к. он виртуальный. Но вызывается A::Destroy()...
Класс B наследует деструктор класса A.
Но в нем, по идее, должен вызываться метод B::Destroy(), т.к. он виртуальный. Но вызывается A::Destroy()...
Читать Мейерза до полного просветления!!
З.Ы. И деструктор сделать виртульным.
И на то есть веская причина: в момент когда вызывается деструктор базового класса (A) содержимое производного класса (B) уже разрушено и вызов виртуальной ф-ции может привести к обращению к уже разрушенным данным. Аналогично и для конструктора,- когда вызывается конструктор базового класса, содержимого производного класса ещё не существует.
P.S. Если используешь виртуализацию, деструктор надо делать виртуальным. Это правило хорошего стиля.
Green, спасибо за единственно верный ответ. Про виртуальный деструктор я знаю, просто старался пример упростить.
Green, спасибо за единственно верный ответ. Про виртуальный деструктор я знаю, просто старался пример упростить.
Конечно не знаю!! Не знаю, как заставить вас книги читать, а не задавать тут давно описаные вопросы. И кажется, довольно ясно написал - читай Мейерза!! Написал бы ясно - "не умею униги читать". В 55 эффективных решений для С++ описан твой вопрос от А до Я.
К модератору просьба - закрыть тему, ввиду полного нежелания топиккастера делать что-либо самому.
Признаюсь, Мейерза не читал.
Зато читал Либерти, Саттера, Страуструпа, Уэллина, даже Кернигана с Риччи, еще каких-то.
Но это было лет 5 назад. В то время я мог похвастаться, что знаю C++ от А до Я.
Но со временем все забываешь (если, конечно, не занимаешься этим постоянно).
А что плохого, если форум помогает человеку получить быстрый ответ не переворачивая всю домашнюю библиотеку?
Это одна из его прямых функций.
Так что давайте жить дружно
P.S.
А я придумал один изврат. Надо написать базовый класс на обжект паскале. Там порядок вызовов конструкторов/деструкторов обратный, следовательно, не должно быть такого ограничения.
Если серьезно, то это довольно хреновый минус языка.
Если серьезно, то это довольно хреновый минус языка.
Если ты про то, что из деструктора нельзя вызывать виртуальные функции, то это не минус языка, а скорее плюс. Точнее даже, что это просто логичная вещь.
Ведь то, что от перестановки мест слагаемых сумма не изменяется, ты же не считаешь хреновым минусом математики.
Я не знаю паскаль, но что-то мне подсказывает, что и в этом языке нельзя (некорректно) обращаться к членам ещё несозданных или разрушенных объектов.
И я уверен, что твой "изврат" можно реализовать другими способами, нежели вызовом виртуальной ф-ции из деструктора. Расскажи про задачу и мы поможем, чем сможем.
Признаюсь, Мейерза не читал.
Зато читал Либерти, Саттера, Страуструпа, Уэллина, даже Кернигана с Риччи, еще каких-то.
Это хорошо, но С++ развивается постоянно.
А что плохого, если форум помогает человеку получить быстрый ответ не переворачивая всю домашнюю библиотеку?
Это одна из его прямых функций.
Мне кажется вы немного путаете форум и справочник. Форум дает ответы на интересные и неочевидные вопросы, или вопросы, требующие креативного подхода. Но, никто не станет цитировать в сотый раз специально для вас книгу. Согласитесь - это скучная работа, которая никоим образом не развивает участников форума.
Так что давайте жить дружно
А я и не ссорился ни с кем.
P.S.
А я придумал один изврат. Надо написать базовый класс на обжект паскале. Там порядок вызовов конструкторов/деструкторов обратный, следовательно, не должно быть такого ограничения.
Я не писал на обжект паскале, но мне кажется очень подозрительным, то что вы говорите. Как можно инициализировать дочерний объект, если в нем не инициализированы родительские поля? Это с точки зрения логики чревато ошибками.
В ObjectPascal нужно руками вызывать метод предка (а также конструктор и деструктор, так как это обычные методы с т.з. языка) в самом теле метода, при том в любом месте, отсюда и "обратный" порядок вызовов.
Согласен.
Ладно, теперь вопрос плавно перетекает в проблему реализации.
Вот простой пример того, что мне нужно сделать (пример нерабочий):
{
TMySuppaList List;
public:
// ...
virtual void ClearList() = 0;
virtual ~A()
{
ClearList(); // имеется в виду уже перегруженный в наследнике
}
};
class B: public A
{
public:
// ...
virtual void ClearList()
{
// очистка List
// для каждого наследника будут разные действия
}
};
Метод ClearList будет часто использоваться сам по себе
Ладно, теперь вопрос плавно перетекает в проблему реализации.
Вот простой пример того, что мне нужно сделать (пример нерабочий):
{
TMySuppaList List;
public:
// ...
virtual void ClearList() = 0;
virtual ~A()
{
ClearList(); // имеется в виду уже перегруженный в наследнике
}
};
class B: public A
{
public:
// ...
virtual void ClearList()
{
// очистка List
// для каждого наследника будут разные действия
}
};
Метод ClearList будет часто использоваться сам по себе
Почему List объявлен в родителе, а очищается в наследнике?
Да и как наследник получает к нему доступ.
Правильнее будет сделать так, чтоб в каком классе данные объявлены, тот класс и оперировал им. Называется это ИНКАПСУЛЯЦИЯ - один из трех китов ООП.
Ну даже если уж очень надо сделать так, то зачем делать это через задний проход? :)
Если можно значительно проще и очевиднее:
{
TMySuppaList List;
public:
// ...
virtual void ClearList() = 0;
};
class B: public A
{
public:
virtual ~B()
{
ClearList(); // имеется в виду уже перегруженный в наследнике
}
// ...
virtual void ClearList()
{
// очистка List
// для каждого наследника будут разные действия
}
};
{
public:
void (*p)();
A()
{
p = 0;
}
virtual ~A()
{
cout << "~A" << endl;
(*p)();// может указывать на статическую функцию наследника
}
};
class B: public A
{
public:
static void ClearList()
{
cout << "ClearList" << endl;
}
B()
{
p = ClearList;
}
};
int main()
{
B b;
return 0;
}
P.S.
А я придумал один изврат. Надо написать базовый класс на обжект паскале. Там порядок вызовов конструкторов/деструкторов обратный, следовательно, не должно быть такого ограничения.
Мне кажется, или для решения проблемы без изврата достаточно сделать две вещи: сделать конструктор виртуальным и переопределить его в наследнике, вызвав Destroy там?
И сделал для себя кое-какой вывод:
если на каком-то этапе разработки видится необходимость в изврате, обхождении препятствий, обусловленных правилами языка, то проблема скорее всего в неправильном выстраивании бизнес-логики.
И сделал для себя кое-какой вывод:
если на каком-то этапе разработки видится необходимость в изврате, обхождении препятствий, обусловленных правилами языка, то проблема скорее всего в неправильном выстраивании бизнес-логики.
Очень правильный вывод.