C ++: последствия создания метода виртуальным - PullRequest
2 голосов
/ 30 ноября 2009

Должен быть вопрос новичка ...

У меня есть существующий код в существующем классе A, который я хочу расширить для переопределения существующего метода A :: f ().

Итак, теперь я хочу создать класс B для переопределения f (), поскольку я не хочу просто изменять A :: f (), потому что от него зависит другой код.

Чтобы сделать это, мне нужно изменить A :: f () на виртуальный метод, я считаю.

У меня вопрос, кроме того, что он позволяет динамически вызывать метод (для использования реализации B, а не A). Существуют ли какие-либо другие последствия для превращения метода в виртуальный? Я нарушаю какую-то хорошую практику программирования? Повлияет ли это на любой другой код, пытающийся использовать A :: f ()?

Пожалуйста, дайте мне знать.

Спасибо, JBU

edit: мой вопрос был скорее похож на то, есть ли что-то неправильное в том, чтобы сделать виртуальный метод кого-то другого даже если вы не изменяете чью-либо реализацию, вам все равно приходится заходить в чей-то существующий код и вносить изменения в объявление.

Ответы [ 8 ]

6 голосов
/ 30 ноября 2009

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

Будучи виртуальным, если вы создадите экземпляр A, он все равно будет вызывать A::f.

Если вы создаете экземпляр B и сохраняете его в указателе типа A*. И тогда вы позвоните A*::->f, тогда он позвонит B B::f.

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

Существует также очень небольшой побочный эффект, может существовать класс C, который также является производным от A, и он может реализовывать C::f и ожидать, что если вызывается A*::->f, то он ожидает, что A::f называться. Но это не очень распространено.

Но, скорее всего, если существует C, то он вообще не реализует C::f, и в этом случае все в порядке.


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

Чтобы избежать побочных эффектов, вы можете сделать следующее:

  1. Создайте тип A2, производный от A, и сделайте его f виртуальным.
    • Используйте указатели типа A2 вместо A
    • Производное B от типа A2.
    • Таким образом, все, что использовало А, будет работать таким же образом, гарантировано

В зависимости от того, что вам нужно, вы также можете использовать has-a отношение вместо is-a.

4 голосов
/ 30 ноября 2009

Для этого мне нужно изменить A :: f () на виртуальный метод, я считаю.

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

Из-за vtable (кроме того, что упомянул spoulson) есть и дополнительная память для виртуальных функций

4 голосов
/ 30 ноября 2009

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

2 голосов
/ 30 ноября 2009

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

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

Если вы собираетесь использовать класс B вместо класса A, то вы можете просто переопределить функцию, не делая ее виртуальной. Если вы собираетесь создавать объекты класса B и ссылаться на них как на указатели на A, то вам нужно сделать f () виртуальным. Вы также должны сделать деструктор виртуальным.

2 голосов
/ 30 ноября 2009

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

  • vtable диспетчеризация, не о чем беспокоиться
  • виртуальные функции никогда не встроены, это может быть намного хуже, чем предыдущая, функция вставки - это то, что действительно может ускорить процесс в некоторых ситуациях, этого никогда не случится с виртуальной функцией.
2 голосов
/ 30 ноября 2009

Есть и другие способы достижения вашей цели. Имеет ли смысл B быть A? Например, имеет смысл для кошки быть животным, но не для кошки быть собакой. Возможно, и A, и B должны быть производными от базового класса, если они связаны между собой.

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

Что касается стоимости, если вы используете A ad B напрямую, компиляция обойдет любую виртуальную диспетчеризацию и просто перейдет к вызовам функций, как будто они никогда не были виртуальными. Если вы передадите B в место, ожидающее `A1 (в качестве ссылки или указателя), тогда оно должно будет отправить.

1 голос
/ 30 ноября 2009

Хорошей практикой программирования является использование виртуальных методов там, где они заслуживают. Виртуальные методы имеют много последствий для понимания того, насколько разумен ваш класс C ++.

Без виртуальных функций вы не можете создавать интерфейсы в C ++. Интерфейс - это класс со всеми неопределенными виртуальными функциями.

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

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

C ++ имеет много ошибок, поэтому нужно очень хорошо понимать, что они хотят делать и как это лучше всего сделать. Существует не так много способов сделать что-либо, как кажется, по сравнению с динамическими языками программирования OO во время выполнения, такими как Java или C #. Некоторые способы будут либо совершенно неправильными, либо со временем приведут к неопределенному поведению по мере развития вашего кода.

Так как вы задали очень хороший вопрос: D, я предлагаю вам купить книгу Скотта Майера: Эффективный C ++ и книгу Бьярна Страуструпа: Язык программирования C ++. Они научат вас тонкостям OO в C ++, особенно когда использовать какую функцию.

0 голосов
/ 30 ноября 2009

Если это первый виртуальный метод, который будет иметь класс, то вы больше не делаете его POD. Это может сломать вещи, хотя шансы на это невелики.

POD: http://en.wikipedia.org/wiki/Plain_old_data_structures

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