Зачем беспокоиться о виртуальных функциях в C ++? - PullRequest
7 голосов
/ 04 марта 2011

Это не вопрос о том, как они работают и декларируются, это, мне кажется, мне довольно понятно.Вопрос в том, зачем это реализовывать?Я полагаю, что практическая причина состоит в том, чтобы упростить кучу другого кода, чтобы связать и объявить их переменные базового типа, обрабатывать объекты и их конкретные методы из многих других подклассов?

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

Прошу прощения за глупые вопросы, но я пока не делал никаких реальных проектов на C ++, так как я активен. ЦельРазработчик на C (это гораздо меньший язык, поэтому он сильно зависит от функциональных возможностей SDK, таких как OSX, iOS). Мне нужно иметь четкое представление о любых параллельных способах обоих двоюродных братьев.

Ответы [ 6 ]

5 голосов
/ 04 марта 2011

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

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

1 голос
/ 04 марта 2011

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

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

Что касается стоимости, связанной с виртуальным вызовом, в основном (во всех реализациях с таблицей виртуальных методов)) вызов виртуальной функции foo, примененной к объекту o: o.foo() переводится в o.vptr[ 3 ](), где 3 - это позиция foo в виртуальной таблице, и это постоянная времени компиляции,По сути, это двойное косвенное указание:

Из объекта o получить указатель на vtable, проиндексировать эту таблицу, получить указатель на функцию, а затем вызвать.Дополнительные затраты по сравнению с прямым неполиморфным вызовом - это просто поиск по таблице.(На самом деле могут быть и другие скрытые затраты при использовании множественного наследования, поскольку может потребоваться смещение неявного указателя this), но стоимость виртуальной отправки очень мала.

1 голос
/ 04 марта 2011

Я не знаю в первую очередь об Objective-C, но вот почему вы хотите «объявить объект как один класс и создать его экземпляр как другой»: Принцип подстановки Лискова .

Поскольку PDF является документом , а документ OpenOffice.org является документом , а документ Word является документом , вполне естественноwrite

Document *d;
if (ends_with(filename, ".pdf"))
    d = new PdfDocument(filename);
else if (ends_with(filename, ".doc"))
    d = new WordDocument(filename);
else
    // you get the point
d->print();

Теперь, чтобы это работало, print должен быть virtual, или быть реализован с использованием virtual функций, или реализован с использованием грубого хака, который заново изобретает virtualрулевое колесо.Программа должна знать во время выполнения , какой из различных print методов применить.

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

0 голосов
/ 04 марта 2011

Использование шаблонов также технически известно как полиморфизм от теоретиков.Да, оба верны в подходе к проблеме.Используемая техника реализации объяснит для них лучшую или худшую производительность.

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

C ++ имеет очень мощные шаблоны.Использование шаблонов делает код быстрее, хотя каждое использование шаблона создает его для данного типа.Это означает, что если вы используете std :: vector для ints, double и string, у вас будет три разных векторных класса: это означает, что размер исполняемого файла пострадает.

0 голосов
/ 04 марта 2011

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

Это похоже на множество функций: способы скрыть то, что вы не хотите делать сами. Это абстракция.

Наследование, интерфейсы и т. Д. Позволяют предоставить компилятору интерфейс для соответствия кода реализации.

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

class A
{
    void do_something();   
};

class B : public A
{
    void do_something(); // this one "hide" the A::do_something(), it replace it.
};


void DoSomething( A* object )
{
    // calling object->do_something will ALWAYS call A::do_something()
    // that's not what you want if object is B...
    // so we have to check manually:

    B* b_object = dynamic_cast<B*>( object );

    if( b_object != NULL ) // ok it's a b object, call B::do_something();
    {
        b_object->do_something()
    }
    else
    {
        object->do_something(); // that's a A, call A::do_something();
    }
}

Здесь есть несколько проблем:

  1. Вы должны написать это для каждой функции, переопределенной в иерархии классов.
  2. у вас есть один дополнительный пункт if для каждого дочернего класса.
  3. вам нужно снова касаться этой функции каждый раз, когда вы добавляете определение ко всей иерархии.
  4. это видимый код, вы можете легко ошибиться, каждый раз

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

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

0 голосов
/ 04 марта 2011

Виртуальная функция важна в наследовании. Вспомните пример, где у вас есть класс CMonster, а затем классы CRaidBoss и CBoss, которые наследуются от CMonster.

Оба должны быть нарисованы. CMonster имеет функцию Draw (), но способ рисования CRaidBoss и CBoss различен. Таким образом, реализация предоставляется им с помощью виртуальной функции Draw.

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