Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

Один из способов прочитать ваши вопросы: "Почему С++ не делает каждую функцию виртуальной по умолчанию, если программист не переопределяет это значение по умолчанию". Не консультируясь с моей копией "Design and Evolution of С++": это добавит дополнительное хранилище для каждого класса, если только каждая функция-член не является виртуальной. Мне кажется, это потребовало бы больших усилий в реализации компилятора и замедлило принятие С++, предоставляя корм для одержимой производительности (я считаю себя в этой группе.)

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

Ответ 2

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

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

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

См. "Дизайн и эволюция С++" для большего обоснования дизайна.

Ответ 3

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

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

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

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

Ответ 4

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

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

Примеры вещей, которые вы можете сделать с помощью экземпляра класса POD:

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

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

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

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

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

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

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

Ответ 5

Следующий пост - это в основном мнение, но здесь говорится:

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

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

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

Ответ 6

Идиома не виртуального интерфейса использует не виртуальные методы. Для получения дополнительной информации обратитесь к статье "Виртуальность" Херба Саттера.

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 [См. раздел]

Ответ 7

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

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

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

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