it-swarm.com.ru

Зачем нам нужны виртуальные функции в C ++?

Я изучаю C++, и я только вхожу в виртуальные функции.

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

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

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

1136
Jake Wilson

Вот как я понял не только то, что --- virtual функции, но почему они необходимы:

Допустим, у вас есть эти два класса:

class Animal
{
    public:
        void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

В вашей основной функции:

Animal *animal = new Animal;
Cat *cat = new Cat;

animal->eat(); // Outputs: "I'm eating generic food."
cat->eat();    // Outputs: "I'm eating a rat."

Пока все хорошо, правда? Животные едят обычную пищу, кошки едят крыс, все без virtual.

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

// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }

Теперь наша основная функция:

Animal *animal = new Animal;
Cat *cat = new Cat;

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating generic food."

Э-э-э ... мы передали Кота в func(), но он не будет есть крыс. Если вы перегружаете func(), чтобы он занимал Cat*? Если вам нужно извлечь больше животных из Animal, им всем понадобится собственная функция func().

Решение состоит в том, чтобы сделать eat() из класса Animal виртуальной функцией:

class Animal
{
    public:
        virtual void eat() { std::cout << "I'm eating generic food."; }
};

class Cat : public Animal
{
    public:
        void eat() { std::cout << "I'm eating a rat."; }
};

Главный:

func(animal); // Outputs: "I'm eating generic food."
func(cat);    // Outputs: "I'm eating a rat."

Готово.

2514
M Perry

Без "виртуального" вы получаете "раннее связывание". Какая реализация метода используется, определяется во время компиляции на основе типа указателя, через который вы вызываете.

С "виртуальным" вы получаете "позднее связывание". Какая реализация метода используется, определяется во время выполнения в зависимости от типа указываемого объекта - как он был изначально создан. Это не обязательно то, что вы думаете, основываясь на типе указателя, который указывает на этот объект.

class Base
{
  public:
            void Method1 ()  {  std::cout << "Base::Method1" << std::endl;  }
    virtual void Method2 ()  {  std::cout << "Base::Method2" << std::endl;  }
};

class Derived : public Base
{
  public:
    void Method1 ()  {  std::cout << "Derived::Method1" << std::endl;  }
    void Method2 ()  {  std::cout << "Derived::Method2" << std::endl;  }
};

Base* obj = new Derived ();
  //  Note - constructed as Derived, but pointer stored as Base*

obj->Method1 ();  //  Prints "Base::Method1"
obj->Method2 ();  //  Prints "Derived::Method2"

РЕДАКТИРОВАТЬ - см. этот вопрос .

Также - это руководство охватывает раннее и позднее связывание в C++.

603
Steve314

Вам нужен как минимум 1 уровень наследования и принижение, чтобы продемонстрировать это. Вот очень простой пример:

class Animal
{        
    public: 
      // turn the following virtual modifier on/off to see what happens
      //virtual   
      std::string Says() { return "?"; }  
};

class Dog: public Animal
{
    public: std::string Says() { return "Woof"; }
};

void test()
{
    Dog* d = new Dog();
    Animal* a = d;       // refer to Dog instance with Animal pointer

    cout << d->Says();   // always Woof
    cout << a->Says();   // Woof or ?, depends on virtual
}
79
Henk Holterman

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

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


Не виртуальный метод ⇒ статическое связывание

Следующий код намеренно "неверен". Он не объявляет метод value как virtual и поэтому выдает непреднамеренный "неправильный" результат, а именно 0:

#include <iostream>
using namespace std;

class Expression
{
public:
    auto value() const
        -> double
    { return 0.0; }         // This should never be invoked, really.
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const
        -> double
    { return number_; }     // This is OK.

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const
        -> double
    { return a_->value() + b_->value(); }       // Uhm, bad! Very bad!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

В строке, отмеченной как "плохой", вызывается метод Expression::value, поскольку статически известный тип (тип, известный во время компиляции) - Expression, а метод value не является виртуальным.


Виртуальный метод ⇒ динамическое связывание.

Объявление value как virtual в статически известном типе Expression гарантирует, что каждый вызов проверит, что это за фактический тип объекта, и вызовет соответствующую реализацию value для этого динамический тип :

#include <iostream>
using namespace std;

class Expression
{
public:
    virtual
    auto value() const -> double
        = 0;
};

class Number
    : public Expression
{
private:
    double  number_;

public:
    auto value() const -> double
        override
    { return number_; }

    Number( double const number )
        : Expression()
        , number_( number )
    {}
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

public:
    auto value() const -> double
        override
    { return a_->value() + b_->value(); }    // Dynamic binding, OK!

    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    {}
};

auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

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

Соответствующая реализация относится к наиболее конкретному (наиболее производному) классу.

Обратите внимание, что реализации методов в производных классах здесь не помечены virtual, а вместо этого помечены override. Их можно пометить virtual, но они автоматически становятся виртуальными. Ключевое слово override гарантирует, что если нет такого виртуального метода в некотором базовом классе, то вы получите ошибку (что желательно).


Безобразие делать это без виртуальных методов

Без virtual нужно было бы реализовать некоторую версию динамического связывания "Сделай сам" . Именно это, как правило, связано с небезопасным ручным понижением, сложностью и многословностью.

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

#include <iostream>
using namespace std;

class Expression
{
protected:
    typedef auto Value_func( Expression const* ) -> double;

    Value_func* value_func_;

public:
    auto value() const
        -> double
    { return value_func_( this ); }

    Expression(): value_func_( nullptr ) {}     // Like a pure virtual.
};

class Number
    : public Expression
{
private:
    double  number_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    { return static_cast<Number const*>( expr )->number_; }

public:
    Number( double const number )
        : Expression()
        , number_( number )
    { value_func_ = &Number::specific_value_func; }
};

class Sum
    : public Expression
{
private:
    Expression const*   a_;
    Expression const*   b_;

    static
    auto specific_value_func( Expression const* expr )
        -> double
    {
        auto const p_self  = static_cast<Sum const*>( expr );
        return p_self->a_->value() + p_self->b_->value();
    }

public:
    Sum( Expression const* const a, Expression const* const b )
        : Expression()
        , a_( a )
        , b_( b )
    { value_func_ = &Sum::specific_value_func; }
};


auto main() -> int
{
    Number const    a( 3.14 );
    Number const    b( 2.72 );
    Number const    c( 1.0 );

    Sum const       sum_ab( &a, &b );
    Sum const       sum( &sum_ab, &c );

    cout << sum.value() << endl;
}

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

40
Cheers and hth. - Alf

Виртуальные функции используются для поддержки Runtime Polymorphism.

То есть ключевое слово virtual указывает компилятору не принимать решение (о привязке функции) во время компиляции, а отложить на время выполнения ".

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

     class Base
     {
        virtual void func();
     }
    
  • Когда Базовый класс имеет виртуальную функцию-член, любой класс, который наследуется от Базового класса, может переопределить функцию с точно таким же прототипом т.е. только функциональность может быть переопределена, а не интерфейс функции.

     class Derive : public Base
     {
        void func();
     }
    
  • Указатель базового класса может использоваться для указания на объект базового класса, а также на объект производного класса.

  • Когда виртуальная функция вызывается с использованием указателя базового класса, компилятор решает во время выполнения, какая версия функции - то есть версия базового класса или переопределенная версия производного класса - должна быть вызвана. Это называется Runtime Polymorphism.
35
user6359267

Если базовый класс - Base, а производный класс - Der, у вас может быть указатель Base *p, который фактически указывает на экземпляр Der. Когда вы вызываете функцию p->foo();, если foo не является виртуальной, то выполняется версия Base, игнорируя тот факт, что p фактически указывает на Der. Если foo является виртуальным, p->foo() выполняет "крайний лист" переопределения foo, полностью принимая во внимание фактический класс указанного элемента. Таким образом, разница между виртуальным и не виртуальным на самом деле довольно существенна: первый допускает время выполнения полиморфизм , базовую концепцию OO программирования, а второй - нет.

33
Alex Martelli

Потребность в виртуальной функции объяснена [легко понять]

#include<iostream>

using namespace std;

class A{
public: 
        void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
     void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B; // Create a base class pointer and assign address of derived object.
    a1->show();

}

Результат будет:

Hello from Class A.

Но с виртуальной функцией:

#include<iostream>

using namespace std;

class A{
public:
    virtual void show(){
        cout << " Hello from Class A";
    }
};

class B :public A{
public:
    virtual void show(){
        cout << " Hello from Class B";
    }
};


int main(){

    A *a1 = new B;
    a1->show();

}

Результат будет:

Hello from Class B.

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

26
Ajay GU

Вы должны различать перегрузку и перегрузку. Без ключевого слова virtual вы перегружаете только метод базового класса. Это ничего не значит, кроме сокрытия. Допустим, у вас есть базовый класс Base и производный класс Specialized, которые оба реализуют void foo(). Теперь у вас есть указатель на Base, указывающий на экземпляр Specialized. Когда вы вызываете foo() для него, вы можете наблюдать разницу, которую делает virtual: Если метод виртуальный, будет использоваться реализация Specialized, если он отсутствует, будет выбрана версия из Base. Лучше никогда не перегружать методы из базового класса. Создание метода, не являющегося виртуальным, позволяет его автору сказать, что его расширение в подклассах не предназначено.

22
h0b0

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

ВИРТУАЛЬНЫЙ РАЗРУШИТЕЛЬ

Рассмотрим эту программу ниже, не объявляя деструктор Базового класса как виртуальный; память для кошки не может быть очищена.

class Animal {
    public:
    ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat() {
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Результат:

Deleting an Animal
class Animal {
    public:
    virtual ~Animal() {
        cout << "Deleting an Animal" << endl;
    }
};
class Cat:public Animal {
    public:
    ~Cat(){
        cout << "Deleting an Animal name Cat" << endl;
    }
};

int main() {
    Animal *a = new Cat();
    delete a;
    return 0;
}

Результат:

Deleting an Animal name Cat
Deleting an Animal
21
Aryaman Gupta

Зачем нам нужны виртуальные методы в C++?

Быстрый ответ:

  1. Это дает нам один из необходимых "ингредиентов"1 для объектно-ориентированное программирование.

В Bjarne Stroustrup C++ Программирование: принципы и практика, (14.3):

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

  1. Это самая быстрая и эффективная реализация, если вам нужно вызов виртуальной функции 2,.

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


1. Использование наследования, полиморфизма во время выполнения и инкапсуляции является наиболее распространенным определением объектно-ориентированного программирования.

2. Вы не можете кодировать функциональность, чтобы быть быстрее или использовать меньше памяти, используя другие языковые функции для выбора среди альтернатив во время выполнения. Бьярне Страуструп Программирование на C++: принципы и практика. (14.3.1),.

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

18
Ziezi

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

переопределение метода: в производном классе дается новая реализация метода базового класса. не облегчить Dynamic binding.

Переопределение метода: Redefining a virtual method базового класса в производном классе. Виртуальный метод облегчает динамическое связывание.

Итак, когда вы сказали:

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

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

14
nitin_cherian

У меня есть мой ответ в форме беседы, чтобы быть лучше прочитанным:


Зачем нам нужны виртуальные функции?

Из-за полиморфизма.

Что такое полиморфизм?

Тот факт, что базовый указатель также может указывать на объекты производного типа.

Как это определение полиморфизма приводит к необходимости виртуальных функций?

Ну, через раннее связывание .

Что такое раннее связывание?

Раннее связывание (связывание во время компиляции) в C++ означает, что вызов функции фиксируется перед выполнением программы.

Так что ...?

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

Если это не то, что мы хотим, почему это разрешено?

Потому что нам нужен полиморфизм!

В чем тогда польза от полиморфизма?

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

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

ну, это потому что ты задал свой вопрос слишком рано!

Зачем нам нужны виртуальные функции?

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

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

Почему другая реализация?

Ты с головой! Иди почитай хорошая книга !

Хорошо, подожди, подожди, подожди, зачем использовать базовые указатели, если он/она может просто использовать указатели производного типа? Будь судьей, стоит ли вся эта головная боль? Посмотрите на эти два фрагмента:

// 1:

Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();

// 2:

Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();

Хорошо, хотя я думаю, что 1 все же лучше, чем 2 , вы можно написать 1 так:

// 1:

Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();

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

double totalMonthBenefit = 0;    
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
     totalMonthBenefit += x -> getMonthBenefit();
}

Теперь попробуйте переписать это, без каких-либо головных болей!

double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();

И на самом деле, это может быть еще и надуманным примером!

14
M-J

Это помогает, если вы знаете основные механизмы. C++ формализует некоторые методы кодирования, используемые программистами C, "классы" заменяются на "оверлеи" - структуры с общими разделами заголовка будут использоваться для обработки объектов различных типов, но с некоторыми общими данными или операциями. Обычно базовая структура оверлея (общая часть) имеет указатель на таблицу функций, которая указывает на различный набор процедур для каждого типа объекта. C++ делает то же самое, но скрывает механизмы, то есть ptr->func(...) C++, где func является виртуальным, поскольку C будет (*ptr->func_table[func_num])(ptr,...), где изменения между производными классами являются содержимым func_table. [Не виртуальный метод ptr-> func () просто переводится как mangled_func (ptr, ..).]

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

11
Kev

Ключевое слово virtual сообщает компилятору, что он не должен выполнять раннее связывание. Вместо этого он должен автоматически установить все механизмы, необходимые для выполнения позднего связывания. Для этого типичный компилятор1 создает одну таблицу (называемую VTABLE) для каждого класса, содержащего виртуальные функции. Компилятор помещает адреса виртуальных функций для этого конкретного класса в VTABLE. В каждом классе с виртуальными функциями он тайно размещает указатель, называемый vpointer (сокращенно VPTR), который указывает на VTABLE для этого объекта. Когда вы делаете виртуальный вызов функции через указатель базового класса, компилятор незаметно вставляет код для извлечения VPTR и поиска адреса функции в VTABLE, вызывая, таким образом, правильную функцию и вызывая позднее связывание.

Подробнее в этой ссылке http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html

9
rvkreddy

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

Shape *shape = new Triangle(); 
cout << shape->getName();

В приведенном выше примере Shape :: getName будет вызываться по умолчанию, если только getName () не определен как виртуальный в базовом классе Shape. Это заставляет компилятор искать реализацию getName () в классе Triangle, а не в классе Shape.

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

Наконец, почему виртуальная среда даже необходима в C++, почему бы не сделать ее поведение по умолчанию, как в Java?

  1. C++ основан на принципах "Нулевые накладные расходы" и "Платите за то, что вы используете". Так что он не пытается выполнить динамическую диспетчеризацию для вас, если вам это не нужно.
  2. Чтобы обеспечить больше контроля над интерфейсом. Делая функцию не виртуальной, интерфейс/абстрактный класс может управлять поведением во всех его реализациях.
6
javaProgrammer

Зачем нам нужны виртуальные функции?

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

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

Программа без виртуальных функций:

#include <iostream>
using namespace std;

class father
{
    public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

Результат:

Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years

Программа с виртуальной функцией:

#include <iostream>
using namespace std;

class father
{
    public:
        virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};

class son: public father
{
    public : void get_age() { cout << "son`s age is 26 years" << endl;}
};

int main(){
    father *p_father = new father;
    son *p_son = new son;

    p_father->get_age();
    p_father = p_son;
    p_father->get_age();
    p_son->get_age();
    return 0;
}

Результат:

Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years

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

4
akshaypmurgod

Что касается эффективности, виртуальные функции несколько менее эффективны, чем функции раннего связывания.

"Этот механизм виртуального вызова можно сделать почти таким же эффективным, как и механизм" обычного вызова функции "(в пределах 25%). Его служебная память занимает один указатель в каждом объекте класса с виртуальными функциями плюс один vtbl для каждого такого класса" [- Тур по C++ Бьярне Страуструп]

2
Duke

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

#include <iostream>

using namespace std;

class Basic
{
    public:
    virtual void Test1()
    {
        cout << "Test1 from Basic." << endl;
    }
    virtual ~Basic(){};
};
class VariantA : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantA." << endl;
    }
};
class VariantB : public Basic
{
    public:
    void Test1()
    {
        cout << "Test1 from VariantB." << endl;
    }
};

int main()
{
    Basic *object;
    VariantA *vobjectA = new VariantA();
    VariantB *vobjectB = new VariantB();

    object=(Basic *) vobjectA;
    object->Test1();

    object=(Basic *) vobjectB;
    object->Test1();

    delete vobjectA;
    delete vobjectB;
    return 0;
}
2
user3371350

Ответ ООП: Полиморфизм подтипа

В C++ виртуальные методы необходимы для реализации полиморфизма , точнее подтипа или полиморфизм подтипа , если вы применяете определение из Википедии.

Wikipedia, Subtyping, 2019-01-09: В теории языка программирования подтип (также полиморфизм подтипов или полиморфизм включения) является формой полиморфизма типов, в которой подтип является типом данных, который по некоторому понятию связан с другим типом данных (супертипом). заменяемости, то есть элементы программы, обычно подпрограммы или функции, написанные для работы с элементами супертипа , также могут работать с элементами подтипа.

ПРИМЕЧАНИЕ. Подтип означает базовый класс, а подтип - унаследованный класс.

Дальнейшее чтение относительно Подтип Полиморфизм

Технический ответ: Динамическая отправка

Если у вас есть указатель на базовый класс, то вызов метода (который объявлен как виртуальный) будет отправлен методу фактического класса создаваемого объекта. Вот как Подтип Полиморфизм реализуется в C++.

Дальнейшее чтение Полиморфизм в C++ и Dynamic Dispatch

Ответ реализации: Создает запись vtable

Для каждого модификатора, "виртуального" в методах, компиляторы C++ обычно создают запись в vtable класса, в котором объявлен метод. Вот как обычный компилятор C++ реализует Dynamic Dispatch .

Дальнейшее чтение vtables


Пример кода

#include <iostream>

using namespace std;

class Animal {
public:
    virtual void MakeTypicalNoise() = 0; // no implementation needed, for abstract classes
    virtual ~Animal(){};
};

class Cat : public Animal {
public:
    virtual void MakeTypicalNoise()
    {
        cout << "Meow!" << endl;
    }
};

class Dog : public Animal {
public:
    virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
        cout << "Woof!" << endl;
    }
};

class Doberman : public Dog {
public:
    virtual void MakeTypicalNoise() {
        cout << "Woo, woo, woow!";
        cout << " ... ";
        Dog::MakeTypicalNoise();
    }
};

int main() {

    Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };

    const   int cnAnimals = sizeof(apObject)/sizeof(Animal*);
    for ( int i = 0; i < cnAnimals; i++ ) {
        apObject[i]->MakeTypicalNoise();
    }
    for ( int i = 0; i < cnAnimals; i++ ) {
        delete apObject[i];
    }
    return 0;
}

Вывод примера кода

Meow!
Woof!
Woo, woo, woow! ... Woof!

Диаграмма классов UML примера кода

UML class diagram of code example

2
Jörg 'Wuwei' Brüggmann

Виртуальные методы используются в дизайне интерфейса. Например, в Windows есть интерфейс под названием IUnknown, как показано ниже:

interface IUnknown {
  virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
  virtual ULONG   AddRef () = 0;
  virtual ULONG   Release () = 0;
};

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

2
user2074102

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

class Base { virtual void foo(); };

class Derived : Base 
{ 
  void foo(); // this is overriding Base::foo
};

Если вы не используете 'virtual' в объявлении Base для foo, то Derived's foo просто будет его скрывать.

1
edwinc

Вот объединенная версия кода C++ для первых двух ответов.

#include        <iostream>
#include        <string>

using   namespace       std;

class   Animal
{
        public:
#ifdef  VIRTUAL
                virtual string  says()  {       return  "??";   }
#else
                string  says()  {       return  "??";   }
#endif
};

class   Dog:    public Animal
{
        public:
                string  says()  {       return  "woof"; }
};

string  func(Animal *a)
{
        return  a->says();
}

int     main()
{
        Animal  *a = new Animal();
        Dog     *d = new Dog();
        Animal  *ad = d;

        cout << "Animal a says\t\t" << a->says() << endl;
        cout << "Dog d says\t\t" << d->says() << endl;
        cout << "Animal dog ad says\t" << ad->says() << endl;

        cout << "func(a) :\t\t" <<      func(a) <<      endl;
        cout << "func(d) :\t\t" <<      func(d) <<      endl;
        cout << "func(ad):\t\t" <<      func(ad)<<      endl;
}

Два разных результата:

Без #define virtual он связывается во время компиляции. Animal * ad и func (Animal *) - все они указывают на метод Animal () с названием ().

$ g++ virtual.cpp -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  ??
func(a) :       ??
func(d) :       ??
func(ad):       ??

С помощью #define virtual он связывается во время выполнения. Dog * d, Animal * ad и func (Animal *) указывают/ссылаются на метод say () Dog, поскольку Dog является их типом объекта. Если метод [Dog's say () "woof"] не определен, то он будет первым, который ищется в дереве классов, то есть производные классы могут переопределять методы своих базовых классов [Animal's say ()].

$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual 
Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

Интересно отметить, что все атрибуты класса (данные и методы) в Python фактически являются виртуальными . Поскольку все объекты создаются динамически во время выполнения, нет объявления типа или необходимости в ключевом слове virtual. Ниже приведена версия кода Python:

class   Animal:
        def     says(self):
                return  "??"

class   Dog(Animal):
        def     says(self):
                return  "woof"

def     func(a):
        return  a.says()

if      __== "__main__":

        a = Animal()
        d = Dog()
        ad = d  #       dynamic typing by assignment

        print("Animal a says\t\t{}".format(a.says()))
        print("Dog d says\t\t{}".format(d.says()))
        print("Animal dog ad says\t{}".format(ad.says()))

        print("func(a) :\t\t{}".format(func(a)))
        print("func(d) :\t\t{}".format(func(d)))
        print("func(ad):\t\t{}".format(func(ad)))

Результат:

Animal a says       ??
Dog d says      woof
Animal dog ad says  woof
func(a) :       ??
func(d) :       woof
func(ad):       woof

который идентичен виртуальному определению C++. Обратите внимание, что d и ad - две разные переменные-указатели, ссылающиеся на один и тот же экземпляр Dog. Выражение (ad is d) возвращает True, и их значения одинаковы < main . Объект Dog в 0xb79f72cc>.

1
Leon Chang

Вы знакомы с указателями функций? Виртуальные функции представляют собой аналогичную идею, за исключением того, что вы можете легко привязать данные к виртуальным функциям (в качестве членов класса). Привязать данные к указателям на функцию не так просто. Для меня это главное концептуальное отличие. Многие другие ответы здесь просто говорят "потому что ... полиморфизм!"

0
user2445507

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

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
  };

 class derived: public base {
 public:
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main () {
      base hwOne;
      derived hwTwo = new derived();
      base->helloWorld(); //prints "Hello World!"
      derived->helloWorld(); //prints "Hello World!"

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

 #include <iostream>
 using namespace std;

 class base {
 public:
 void helloWorld() { std::cout << "Hello World!"; }
 };

 class derived : public base {
 public:
 void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
 void(derived::*hwBase)();
 void helloWorld() { std::cout << "Greetings World!"; }
 };

 int main()
 {
 base* b = new base(); //Create base object
 b->helloWorld(); // Hello World!
 void(derived::*hwBase)() = &derived::helloWorld; //create derived member 
 function pointer to base function
 derived* d = new derived(); //Create derived object. 
 d->displayHWDerived(hwBase); //Greetings World!

 char ch;
 cin >> ch;
 }

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

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

Правка: есть еще один метод, который похож на eddietree: виртуальная функция c ++ против указателя на функцию-член (сравнение производительности) .

0
fishermanhat

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

0
rashedcs