Есть ли причина не делать функцию-член виртуальной?

Взгляните на версию [h0] процесса хакера wj32 , которая может делать то, что вы просили, и многое другое.

21
задан Jason Baker 15 November 2008 в 03:57
поделиться

7 ответов

Один из способов прочитать ваши вопросы: «Почему C ++ не делает каждую функцию виртуальной по умолчанию, если программист не переопределяет эту настройку по умолчанию». Без консультации с моей копией «Дизайн и эволюция C ++»: это добавило бы дополнительное хранилище для каждого класса, если каждая функция-член не сделана не виртуальной. Мне кажется, это потребовало бы больше усилий в реализации компилятора и замедлило принятие C ++, предоставляя корм для одержимой производительности (я считаю себя в этой группе).

Еще один способ прочитать ваши вопросы «Почему программисты на C ++ не делают каждую функцию виртуальной, если у них нет веских причин не делать этого?» Возможно, причина в производительности. В зависимости от вашего приложения и домена, это может быть хорошей причиной или нет. Например, часть моей команды работает на предприятиях по продаже рыночных данных. При скорости более 100 000 сообщений в секунду в одном потоке издержки виртуальной функции будут неприемлемы. Другие части моей команды работают в сложной торговой инфраструктуре. В этом контексте, вероятно, хорошей идеей является виртуализация большинства функций, поскольку дополнительная гибкость превосходит микрооптимизацию.

23
ответ дан coryan 15 November 2008 в 03:57
поделиться

Страуструп, разработчик языка, говорит :

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

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

См. Дизайн и развитие C ++ для более детального обоснования дизайна.

23
ответ дан Shog9 15 November 2008 в 03:57
поделиться

В идиоме не-виртуального интерфейса используются не-виртуальные методы. Для получения дополнительной информации, пожалуйста, обратитесь к статье Herb Sutter «Виртуальность».

http://www.gotw.ca/publications/mill18.htm

И комментарии к идиоме NVI:

http: // www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.3 http://accu.org/index.php/journals/269 [см. подраздел ]

3
ответ дан 15 November 2008 в 03:57
поделиться

Я проигнорирую производительность и стоимость памяти, потому что у меня нет способа измерить их для «общего» случая ...

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

Примеры того, что вы можете переносить с экземпляром класса POD:

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

Другие вещи, о которых я упоминал, с которыми я согласен:

  • Многие классы не предназначены для наследования. Виртуализация их методов вводит в заблуждение, поскольку подразумевает, что дочерние классы могут захотеть переопределить метод, и не должно быть никаких дочерних классов.

  • Многие методы не предназначены для переопределения: то же самое.

Кроме того, даже когда вещи предназначены для подкласса / переопределения, они не обязательно предназначены для полиморфизма во время выполнения. Очень редко, несмотря на то, что говорят лучшие практики ОО, для наследования требуется повторное использование кода. Например, если вы используете CRTP для имитации динамического связывания. Итак, опять же, вы не хотите подразумевать, что ваш класс будет хорошо играть с полиморфизмом во время выполнения, делая его методы виртуальными, когда их никогда не следует так называть.

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

Это сложная проблема при разработке общедоступного API, потому что переключение метода с одного на другой является серьезным изменением, поэтому вы должны сделать это правильно с первого раза. Но вы не обязательно знаете, прежде чем у вас появятся пользователи, захотят ли ваши пользователи «полиморфировать» ваши классы. Хо гул. Контейнерный подход STL, определяющий абстрактные интерфейсы и полностью запрещающий наследование, безопасен, но иногда требует, чтобы пользователи больше печатали.

8
ответ дан Steve Jessop 15 November 2008 в 03:57
поделиться

Следующее сообщение является главным образом мнением, но здесь идет:

Объектно-ориентированное проектирование является тремя вещами, и инкапсуляция (сокрытие информации) является первой из этих вещей. Если дизайн класса не серьезен на этом, то остальное действительно не имеет значения очень.

Это было сказано перед той "инкапсуляцией повреждений наследования" (Alan Snyder '86), хорошее обсуждение этого присутствует в группе четырех книг шаблона разработки. Класс должен быть разработан для поддержки наследования очень определенным способом. Иначе Вы открываете возможность неправильного употребления наследниками.

я сделал бы аналогию, что создание всех Ваших виртуальных методов сродни тому, чтобы обнародовать всех Ваших участников. Что-то вроде фрагмента, я знаю, но вот почему я использовал слово 'аналогия'

5
ответ дан Matt Brunell 15 November 2008 в 03:57
поделиться

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

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

На уровне класса, делая деструктор невиртуальным передает это, класс не должен использоваться в качестве базового класса, такого как контейнеры STL.

Создание невиртуального метода связывается, как оно должно использоваться.

2
ответ дан JohnMcG 15 November 2008 в 03:57
поделиться

Существует несколько причин.

Первый, производительность: Да, издержки виртуальной функции относительно низко замечены в изоляции. Но это также препятствует тому, чтобы компилятор встроил, и это огромно источник оптимизации в C++. Библиотека стандарта C++ работает, а также она делает, потому что она может встроить десятки и десятки маленьких острот, из которых она состоит. Кроме того, класс с виртуальными методами не является типом данных POD, и таким образом, много ограничений относится к нему. Это не может быть скопировано только memcpy'ing, это становится более дорогим для построения и занимает больше места. Существует много вещей, которые внезапно становятся недопустимыми или менее эффективными, как только тип не-POD включен.

И во-вторых, хорошая практика ООП. Точка в классе - то, что он делает некоторую абстракцию, скрывает ее внутренние детали и обеспечивает гарантию, что "этот класс будет вести себя так и так и будет всегда поддерживать эти инварианты. Это будет никогда , заканчиваются в недопустимом состоянии". Этому довольно трудно соответствовать, если Вы позволяете другим переопределять какой-либо функция членства. Функции членства, которые Вы определили в классе, там, чтобы гарантировать, что инвариант сохраняется. Если мы не заботились, о котором, мы могли просто обнародовать внутренние элементы данных, и позволять людям управлять ими по желанию. Но мы хотим, чтобы наш класс был последователен. И это означает, что мы должны определить поведение его открытого интерфейса. Это может включить конкретный точки настраиваемости путем создания отдельных функций виртуальными, но это почти всегда также включает создание большинства невиртуальных методов, так, чтобы они могли сделать задание обеспечения, что инвариант сохраняется. Невиртуальная интерфейсная идиома является хорошим примером этого: http://www.gotw.ca/publications/mill18.htm

В-третьих, наследование не часто необходимо, особенно не в C++. Шаблоны и универсальное программирование (статический полиморфизм) могут во многих случаях сделать лучшее задание, чем наследование (полиморфизм во время выполнения). Да, Вам иногда все еще нужны виртуальные методы и наследование, но это - конечно, не значение по умолчанию. Если это, Вы Делаете Его Неправильно. Работа с языком, вместо того, чтобы попытаться симулировать его была чем-то еще. Это не Java, и в отличие от Java, в C++, наследование является исключением, не правилом.

10
ответ дан jalf 15 November 2008 в 03:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: