Вы упускаете торговые возможности:
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Регистрация
Вход
Вы принимаете политику сайта и условия использования
Если у вас нет учетной записи, зарегистрируйтесь
Виртуальные методы полезны только при использовании указателей. Если указатели не используются, virtual не пригодится.
Да, чаще всего это так.
Однако, не всегда.
Смотри:
У тебя есть класс фигуры, и в нём функция, получающая её диаметр. Плодишь кучу потомков от этого класса. Разных фигур.
А теперь - тебе надо, чтобы класс фигуры умел запросить, скажем, размер листа, на котором она будет нарисована. И этот запрос - функция базового класса. Которая будет вызывать функцию диаметра.
И вот, ты создал фигуру-потомка, вызываешь у неё функцию размера листа. И вот тут, если функция запроса диаметра будет невиртуальна - то ты в диаметре получишь не свою функцию потомка, а функцию базового класса. Только если функция получения диаметра виртуальна - будет выполнена правильная функция получения диаметра.
Заметь - никаких указателей нет.
То-есть функция в потомке просто заменяет функцию родителя?
Если так, то зачем всё это городить если простая функция будет работать так-же… В чём прикол? В чём полезность виртуальности?
Ну в том примере, что привёл я, это не я писал, там более-менее понятно. Внутри классов СArray и СArrayObj неоднократно вызывается метод Compare и в этом случае понятно будет замена. А вот объяснения с классом CTrade совсем не догоняю.
Не просто заменяет. А только в том случае, если ты создаёшь класс-потомок. И именно той виртуальной функцией, которая у тебя есть в классе-потомке.
У тебя может быть большая многоступенчатая иерархия. Ты вызываешь виртуальную функцию базового класса, а при этом будет вызвана функция именно того класса, который был создан.
В случае с классом CTrade виртуальность потребуется, если у тебя будет иерархия классов-потомков.
Функция вполне могла бы быть и невиртуальной, но в этом случае иерархию, в которой бы вызывалась нужная функция наследника создать не удалось. Вызывалась бы только функция базового класса.
Откуда эти функции?
Видимо это не для моих мозгов. Но всё-же я постараюсь понять это.
Эти функции ты пишешь у всех классов в иерархии.
У базового CTrade - функция уже написана.
Ты пишешь наследника CStatTrade - и в нём пишешь такую же функцию, которая сперва собирает статистику, а потом - вызывает базовую.
Далее пишешь наследника ещё выше - CStopStatTrade, и в нём тоже есть такая же функция, но которая умеет по собранной статистике ещё и давать дополнительные приказы, после чего вызывать функцию базового класса CStatTrade (а та, в свою очередь, соберёт статистику, и вызовет функцию CTrade).
Теперь - чисто заменив в коде объект CTrade объектом CStopStatTrade - ты получаешь сразу возможность с помощью той же виртуальной функции посылки ордера - вызвать и дополнительные приказы, и собрать статистику.
То-есть функция в потомке просто заменяет функцию родителя?
Если так, то зачем всё это городить если простая функция будет работать так-же… В чём прикол? В чём полезность виртуальности?
Ну в том примере, что привёл я, это не я писал, там более-менее понятно. Внутри классов СArray и СArrayObj неоднократно вызывается метод Compare и в этом случае понятно будет замена. А вот объяснения с классом CTrade совсем не догоняю.
Попытался обобщить и получилось выделить два сценария, когда слово virtual имеет значение.
Сценарий 1. Необходимо наличие трёх предпосылок:
При этом каждый объект-наследник, несмотря на то, что в массиве он хранится в виде указателя на объект базового класса, будет выполнять код своей собственной реализации виртуального метода(ов). Вот эта способность и называется жутким словом "полиморфизм". Ведь массив хранит элементы только одинакового типа (указатель на объект базового класса), а ведут они себя по-разному при вызове одного и того же метода. Как раз благодаря тому, что вызываемый метод объявлен как виртуальный.
Этот сценарий как раз используется в примерах с управлением разными животными.
Сценарий 2. Тоже необходимо наличие трёх предпосылок:
Алгоритм из п.3 будет использовать указатель на объект базового класса и вызывать для него нужные методы. Но если в ту же переменную, которая раньше содержала указатель на объект базового класса, присвоить теперь указатель на объект класса-наследника, то весь ранее написанный код будет работать. Однако действия внутри вызываемых виртуальных методов теперь будут браться из реализации этих методов в классе наследнике.
Если чего-то похожего на эти сценарии пока нет или не нужно, то добавление слова virtual к описанию любого метода ни на что не повлияет. То есть его можно спокойно добавить, и вреда от этого не будет. Но если virtual не добавить, а в последствии эти три предпосылки окажутся выполнены, то воспользоваться полиморфизмом уже не получится.
Поэтому объявление метода CTrade::OrderSend() как виртуального сделано, на мой взгляд, просто на всякий случай. Мало ли что кому-нибудь в будущем придёт в голову? Вдруг захочется реализовать, например, такой сценарий:
CTrade *trade = new CTrade();Если бы не было слова vitrtual в описании метода CTrade::OrderSend(), то все вызовы в коде trade.OrderSend() по-прежнему использовали бы базовую реализацию, так как в объявлении переменной trade тип остался прежним - указатель на объект класса CTrade.
Конечно, перейти на использование нашего нового класса-наследника можно было и по-другому. Например, поменяв тип указателя для переменной trade:
Или вообще не используя динамический объект:
В последних двух случаях даже без объявления метода CTrade::OrderSend() как виртуального, всё работало бы правильно - вызывалась версия этого метода, реализованная в классе-наследнике.
Но вдруг кто-то захочет использовать именно первый вариант (CTrade *trade = new CMyTrade();)? Или где-то зачем-то этот объект передаётся как параметр внутрь нескольких функций? Тогда в описании параметров каждой такой функции придётся тоже менять тип CTrade на CMyTrade... В общем, если нельзя заранее предугадать все дальнейшие варианты использования кода, то лучше выбрать такую реализацию, которая даёт бОльшую свободу. Добавление virtual к OrderSend() не приносит ограничений, но расширяет потенциальные способы его дальнейшего использования. Наверное, поэтому этот метод объявлен виртуальным, хотя трудно сказать, когда и как это сможет облегчить жизнь программисту из будущего.
Эти функции ты пишешь у всех классов в иерархии.
У базового CTrade - функция уже написана.
Ты пишешь наследника CStatTrade - и в нём пишешь такую же функцию, которая сперва собирает статистику, а потом - вызывает базовую.
Далее пишешь наследника ещё выше - CStopStatTrade, и в нём тоже есть такая же функция, но которая умеет по собранной статистике ещё и давать дополнительные приказы, после чего вызывать функцию базового класса CStatTrade (а та, в свою очередь, соберёт статистику, и вызовет функцию CTrade).
Теперь - чисто заменив в коде объект CTrade объектом CStopStatTrade - ты получаешь сразу возможность с помощью той же виртуальной функции посылки ордера - вызвать и дополнительные приказы, и собрать статистику.
Георгий, даже без объявления виртуальными переопределямых методов базового класса и последующих, вы всегда сможете вызвать из классов потомков именно ту реализацию одного из классов родителей, какую захотите. Достаточно указать имя желаемого родительского класса перед названием метода.
Виртуальность нужна только в случае неопределённости, возникающей из-за того, что переменная типа "указатель на объект базового класса" может также хранить и указатель на объект любого из классов-потомков. Тогда при использовании virtual код будет при вызове метода дополнительно проверять: "А это случайно не какой-то дальний потомок у нас тут лежит? Ой, да, это уже не база. Посмотрим, что там потомок хочет в этом методе вытворить...". А без использования virtual код при вызове будет всех посылать: "У вас эта переменная объявлена как указатель на базовый класс? Вот я и запущу его реализацию этого метода. А то, что это может оказаться потомок, меня не волнует".
Георгий, даже без объявления виртуальными переопределямых методов базового класса и последующих, вы всегда сможете вызвать из классов потомков именно ту реализацию одного из классов родителей, какую захотите. Достаточно указать имя желаемого родительского класса перед названием метода.
Виртуальность нужна только в случае неопределённости, возникающей из-за того, что переменная типа "указатель на объект базового класса" может также хранить и указатель на объект любого из классов-потомков. Тогда при использовании virtual код будет при вызове метода дополнительно проверять: "А это случайно не какой-то дальний потомок у нас тут лежит? Ой, да, это уже не база. Посмотрим, что там потомок хочет в этом методе вытворить...". А без использования virtual код при вызове будет всех посылать: "У вас эта переменная объявлена как указатель на базовый класс? Вот я и запущу его реализацию этого метода. А то, что это может оказаться потомок, меня не волнует".
Да, всё верно, так можно вызывать именно ту функцию, которая требуется.
Однако, на мой взгляд, это плохая практика. Как раз потому, что так - мы теряем ту самую гибкость виртуальности, с вызовом функции именно созданного класса. А если мы жестко укажем - то всегда будет вызываться функция указанного класса, а он может быть совсем не тем, что создан.
При этом каждый объект-наследник, несмотря на то, что в массиве он хранится в виде указателя на объект базового класса, будет выполнять код своей собственной реализации виртуального метода(ов). Вот эта способность и называется жутким словом "полиморфизм". Ведь массив хранит элементы только одинакового типа (указатель на объект базового класса), а ведут они себя по-разному при вызове одного и того же метода. Как раз благодаря тому, что вызываемый метод объявлен как виртуальный.
Массив обявлен как массив указателей на базовый класс, но хранит он реально (совместимые) указатели на фактический класс, если не было явного приведения типа. И вызывается метод фактического типа, или ищется по иерархии родителей.
Да, всё так. Говоря, что "объект-наследник в массиве хранится в виде указателя на объект базового класса" имелось ввиду, что если смотреть по объявлению массива, то в нём лежат указатели на объекты базового класса. Но дальше уже вспоминаем, что указатель на объект базового класса может хранить и указатель на объект любого из производных классов (потомков).
Однако, на мой взгляд, это плохая практика. Как раз потому, что так - мы теряем ту самую гибкость виртуальности, с вызовом функции именно созданного класса. А если мы жестко укажем - то всегда будет вызываться функция указанного класса, а он может быть совсем не тем, что создан.
Мне кажется, имеется недопонимание. Если брать ваш пример с переопределением метода CTrade::OrderSend() в двух потомках CStatTrade и CStopStatTrade, где в каждом выполняются какие-то дополнительные действия, а затем вызывается метод родительского класса, то внутри кода этих методов вы обязаны будете указать, метод какого родителя вы вызываете. Объявление этого метода виртуальным никак не освободит вас от этого. Про вызов этого метода снаружи кода класса речи не шло, только: "... сможете вызвать из классов потомков".
Это дальше уже зависит от того, как мы заменим в коде объект CTrade объектом CStopStatTrade. Если без внесения неопределённости, то и без объявления виртуальным получим, что вызываться будет метод именно из CStopStatTrade. А если создадим новый объект класса CStopStatTrade, но указатель на него запомним в переменной, объявленной как CTrade*, то тогда метод должен быть виртуальным, чтобы вызвалась именно версия потомка, а не CTrade.
Снаружи кода класса нам действительно лучше нигде не использовать прямое указание, метод какого родителя мы вызываем сейчас. Необходимость такого указания с высокой вероятностью говорит о том, что надо как-то по-другому наполнить классы методами.
Попытался обобщить и получилось выделить два сценария, когда слово virtual имеет значение.
Сценарий 1. Необходимо наличие трёх предпосылок:
При этом каждый объект-наследник, несмотря на то, что в массиве он хранится в виде указателя на объект базового класса, будет выполнять код своей собственной реализации виртуального метода(ов). Вот эта способность и называется жутким словом "полиморфизм". Ведь массив хранит элементы только одинакового типа (указатель на объект базового класса), а ведут они себя по-разному при вызове одного и того же метода. Как раз благодаря тому, что вызываемый метод объявлен как виртуальный.
Этот сценарий как раз используется в примерах с управлением разными животными.
Сценарий 2. Тоже необходимо наличие трёх предпосылок:
Алгоритм из п.3 будет использовать указатель на объект базового класса и вызывать для него нужные методы. Но если в ту же переменную, которая раньше содержала указатель на объект базового класса, присвоить теперь указатель на объект класса-наследника, то весь ранее написанный код будет работать. Однако действия внутри вызываемых виртуальных методов теперь будут браться из реализации этих методов в классе наследнике.
Если чего-то похожего на эти сценарии пока нет или не нужно, то добавление слова virtual к описанию любого метода ни на что не повлияет. То есть его можно спокойно добавить, и вреда от этого не будет. Но если virtual не добавить, а в последствии эти три предпосылки окажутся выполнены, то воспользоваться полиморфизмом уже не получится.
Поэтому объявление метода CTrade::OrderSend() как виртуального сделано, на мой взгляд, просто на всякий случай. Мало ли что кому-нибудь в будущем придёт в голову? Вдруг захочется реализовать, например, такой сценарий:
Если бы не было слова vitrtual в описании метода CTrade::OrderSend(), то все вызовы в коде trade.OrderSend() по-прежнему использовали бы базовую реализацию, так как в объявлении переменной trade тип остался прежним - указатель на объект класса CTrade.
Конечно, перейти на использование нашего нового класса-наследника можно было и по-другому. Например, поменяв тип указателя для переменной trade:
Или вообще не используя динамический объект:
В последних двух случаях даже без объявления метода CTrade::OrderSend() как виртуального, всё работало бы правильно - вызывалась версия этого метода, реализованная в классе-наследнике.
Но вдруг кто-то захочет использовать именно первый вариант (CTrade *trade = new CMyTrade();)? Или где-то зачем-то этот объект передаётся как параметр внутрь нескольких функций? Тогда в описании параметров каждой такой функции придётся тоже менять тип CTrade на CMyTrade... В общем, если нельзя заранее предугадать все дальнейшие варианты использования кода, то лучше выбрать такую реализацию, которая даёт бОльшую свободу. Добавление virtual к OrderSend() не приносит ограничений, но расширяет потенциальные способы его дальнейшего использования. Наверное, поэтому этот метод объявлен виртуальным, хотя трудно сказать, когда и как это сможет облегчить жизнь программисту из будущего.
Вот тут стало чуток попонятней. Спасибо.