Если класс может быть унаследован, должна ли каждая функция быть виртуальной? - PullRequest
5 голосов
/ 15 февраля 2011

В C ++ кодер не знает, наследуют ли другие кодеры его класс.Должен ли он сделать каждую функцию в этом классе виртуальной?Есть ли недостатки?Или это совсем не приемлемо?

Ответы [ 7 ]

10 голосов
/ 15 февраля 2011

В C ++ вы должны делать класс наследуемым только в том случае, если вы намереваетесь использовать его полиморфно. То, как вы относитесь к полиморфным объектам в C ++, сильно отличается от того, как вы относитесь к другим объектам. Вы не склонны помещать полиморфные классы в стек, передавать их или возвращать из функций по значению, поскольку это может привести к нарезке . Полиморфные объекты имеют тенденцию выделяться в куче, передаваться и возвращаться по указателю или по ссылке и т. Д.

Если вы разрабатываете класс, который не наследуется, а затем наследуется от него, вы вызываете всевозможные проблемы. Если деструктор не помечен как виртуальный, вы не можете удалить объект через указатель базового класса, не вызвав неопределенного поведения. Без функций-членов, помеченных virtual, они не могут быть переопределены в производном классе.

Как правило, в C ++ при разработке класса определяется, хотите ли вы, чтобы он был унаследован. Если вы это сделаете, отметьте соответствующие функции virtual и присвойте ему деструктор virtual. Вы также можете отключить оператор назначения копирования, чтобы избежать нарезки. Точно так же, если вы хотите, чтобы класс не был наследуемым, не предоставляйте ему ни одну из этих функций. В большинстве случаев это логическая ошибка - наследовать от класса, который не предназначен для наследования, и в большинстве случаев вы можете захотеть сделать это, вы часто можете использовать состав вместо наследования для достижения этот эффект.

4 голосов
/ 15 февраля 2011

Нет, обычно нет.

Не виртуальная функция обеспечивает поведение, инвариантное к классу. Виртуальная функция не делает. Таким образом, человек, пишущий базовый класс, должен подумать о том, должно ли поведение конкретной функции быть / должно быть инвариантным или нет.

Хотя возможно для дизайна, позволяющего варьировать все поведения в производных классах, это довольно необычно. Обычно это довольно хорошая подсказка, что человек, который написал класс, либо не очень задумывался о его дизайне, ему не хватало решимости принять решение.

4 голосов
/ 15 февраля 2011

В C ++ вы проектируете ваш класс для использования в качестве значения типа или полиморфного типа . См., Например, C ++ FAQ .

2 голосов
/ 15 февраля 2011

Джерри Коффин и Доминик МакДоннелл уже охватил наиболее важные моменты.

Я просто добавлю наблюдение, что во времена MFC (средний1990-е) Я был очень раздражен отсутствием способов зацепиться за вещи.Например, документация предложила скопировать исходный код MFC для печати и изменения вместо переопределения поведения.Потому что там ничего не было виртуальным.

Конечно, существует 1 миллион способов предоставить «крючки», но виртуальные методы - это один простой способ.Они нужны в плохо спроектированных классах, чтобы клиентский код мог что-то исправить, но в этих плохо спроектированных классах методы не являются виртуальными.Для классов с лучшим дизайном не так уж много необходимости переопределять поведение, и поэтому для тех классов, которые делают методы виртуальными по умолчанию (и не виртуальными только в качестве активного выбора), это может привести к обратным результатам;как заметил Джерри, виртуальные ресурсы предоставляют возможности для порочных классов.

Существуют шаблоны проектирования, которые можно использовать для минимизации возможностей ошибок.

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

Cheers & hth.,

2 голосов
/ 15 февраля 2011

Если вы создаете класс для использования другими людьми, вы должны тщательно продумать свой интерфейс и попытаться выяснить, как будет использоваться ваш класс.Затем примите решение, например, какие функции должны быть виртуальными.

Или, еще лучше, напишите тестовый пример для вашего класса, используя его так, как вы ожидаете его использовать, а затем заставьте интерфейс работать для этого.Вы можете быть удивлены тем, что узнаете, делая это.Вещи, которые вы считали абсолютно необходимыми, могут оказаться крайне редкими, а вещи, которые, по вашему мнению, не будут использоваться, могут оказаться наиболее полезными методами.Это поможет вам сэкономить время, не выполняя ненужную работу в долгосрочной перспективе, и в итоге получит надежные конструкции.

1 голос
/ 15 февраля 2011

Когда вы создаете класс и хотите, чтобы этот класс использовался полиморфно, вы должны учитывать, что этот класс имеет два разных интерфейса.Пользовательский интерфейс определяется набором открытых функций, доступных в вашем базовом классе, и он должен в значительной степени охватывать все операции, которые пользователи хотят выполнять над объектами вашего класса.Этот интерфейс определяется квалификаторами доступа, в частности квалификатором public.

Есть второй интерфейс, который определяет, как ваш класс должен быть расширен.На этом уровне вы должны подумать о том, какое поведение вы хотите изменить, расширяя классы, и какие элементы вашего объекта вы хотите предоставить расширяющим классам.Вы предлагаете доступ к производным классам с помощью квалификатора protected и предлагаете точки расширения с помощью виртуальных функций.

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

Существует один случай, когда virtual должен быть частью пользовательского интерфейса: деструктор.Если вы хотите предложить своим пользователям возможность уничтожать производные объекты с помощью указателей на базу, то вы должны предоставить виртуальный деструктор.В противном случае вы просто предоставляете защищенный не виртуальный.

0 голосов
/ 15 февраля 2011

Он должен кодировать функции как есть, он вообще не должен делать их виртуальными, как в указанных вами обстоятельствах.

Причины 1> КОДЕР КЛАССА, очевидно, будет иметь определенное использованиефункции, которые он использует.2> Унаследованный класс может использовать или не использовать эти функции в соответствии с требованиями.3> Любая функция может быть перезаписана в производном классе без ошибок.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...