почему у нас на самом деле есть виртуальные функции?

StackOverflow https://stackoverflow.com/questions/2238928

Вопрос

Я новичок в C ++.

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

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

почему у нас на самом деле есть виртуальные функции?

Это было полезно?

Решение

АБСТРАКТНЫЙ

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

НУЛЕВАЯ ЧАСТЬ

Говорят, что метод класса является виртуальный тогда и только тогда, когда это объявлено таковым.

class my_base
{
public:
            void non_virtual_test() { cout << 4 << endl; } // non-virtual
    virtual void virtual_test()     { cout << 5 << endl; } // virtual
};

(Конечно, я предполагаю, что программист ранее не делал ничего подобного #define virtual.)

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

class my_derived : public my_base
{
public:
    void non_virtual_test() { cout << 6 << endl; } // overloaded
    void virtual_test()     { cout << 7 << endl; } // overriden
};

ЧАСТЬ ПЕРВАЯ

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

class my_base
{
//<vtable>
// The vtable is actually a bunch of member function pointers
protected:
    void (my_base::*virtual_test_ptr)();
//</vtable>

// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
    void virtual_test_impl() { cout << 5 << endl; }

// Initializing the real_virtual_test pointer in the vtable.
public:
    my_base() : virtual_test_ptr(&my_base::virtual_test_impl) {}

public:
    void non_virtual_test() { cout << 4 << endl; }
    // The interface of the virtual function is a wrapper
    // around the member function pointer.
    inline void virtual_test() { *virtual_test_ptr(); }
};

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

class my_derived : public my_base
{
// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
    void virtual_test_impl() { cout << 7 << endl; }

// Initializing the real_virtual_test pointer in the vtable.
public:
    my_derived() : virtual_test_ptr(&my_derived::virtual_test_impl) {}

public:
    void non_virtual_test() { cout << 6 << endl; }
};

ЧАСТЬ ВТОРАЯ

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

#include <iostream>

using namespace std;

class my_base
{
    public:
            void non_virtual_test() { cout << 4 << endl; }
    virtual void virtual_test()     { cout << 5 << endl; }
};

class my_derived : public my_base
{
public:
    void non_virtual_test() { cout << 6 << endl; }
    void virtual_test()     { cout << 7 << endl; }
}

int main()
{
    my_base* base_obj = new my_derived();

    // This outputs 4, since my_base::non_virtual_test() gets called,
    // not my_derived::non_virtual_test().
    base_obj->non_virtual_test();

    // This outputs 7, since the vtable pointer points to
    // my_derived::virtual_test(), not to my_base::virtual_test().
    base_obj->virtual_test();

    // We shall not forget
    // there was an object that was pointed by base_obj
    // who happily lived in the heap
    // until we killed it.
    delete base_obj;

    return 0;
}

ЧАСТЬ ТРЕТЬЯ

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

#include <iostream>

using namespace std;

class animal
{
public:
    virtual void say_something()
    { cout << "I don't know what to say." << endl
           << "Let's assume I can growl." << endl; }

    /* A more sophisticated version would use pure virtual functions:
     *
     * virtual void say_something() = 0;
     */
};

class dog : public animal
{
public:
    void say_something() { cout << "Barf, barf..." << endl; }
};

class cat : public animal
{
public:
    void say_something() { cout << "Meow, meow..." << endl; }
};

int main()
{
    animal *a1 = new dog();
    animal *a2 = new cat();
    a1->say_something();
    a2->say_something();
}

Другие советы

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

Подумайте о млекопитающем базового класса с функцией speak.Функция недействительна и просто описывает, как говорит млекопитающее.Когда вы наследуете от этого класса, вы можете переопределить метод speak, чтобы собаки кричали "Арф-арф!", а кошки - "Мяу-мяу".

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

Перегрузка функций означает создание функции с тем же именем, но разными аргументами, т. е.различное количество и тип аргументов.Вот объяснение перегрузки в C ++ из Сайт IBM:

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

Перегруженное объявление - это объявление, которое было объявлено с тем же именем, что и ранее объявленное объявление в той же области, за исключением того, что оба объявления имеют разные типы.

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

Что касается полной рациональной причины для ситуаций, когда требуются виртуальные функции, в этом сообщении в блоге приводится хорошая: http://nrecursions.blogspot.in/2015/06/so-why-do-we-need-virtual-functions.html

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

Базовая настройка

В C ++ любой производный класс может быть передан функции, требующей объекта базового класса.(См . также Нарезка и LSP).Данный:

struct Base_Virtual
{
  virtual void some_virtual_function();
};

struct Base_Nonvirtual
{
  void some_function();
};

void Function_A(Base_Virtual * p_virtual_base);
void Function_B(Base_Nonvirtual * p_non_virtual_base);

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

Объявлены две функции, для которых требуются указатели на соответствующие базовые классы.

Производные классы

Давайте теперь проверим полиморфизм, особенно virtual против.невиртуальный (переопределяющий методы).Структуры:

struct Derived_From_Virtual
: public Base_Virtual
{
  void some_virtual_function(); // overrides Base_Virtual::some_virtual_function()
};

структура, производная от_виртуальной :общедоступная базовая_невиртуальность { аннулирует some_function();}

В соответствии с языком C ++, я могу передать указатель на Derived_From_Virtual Для Function_A потому что Derived_From_Virtual наследуется от Base_Virtual.Я также могу передать указатель на Derived_From_Nonvirtual Для Function_B.

Разница Между virtual и главенствующий

Тот Самый virtual модификатор в Base_Virtual, сообщает компилятору , что Function_A будет использовать Derived_From_Virtual::some_virtual_function() вместо метода в Base_Virtual.Это происходит потому, что метод является виртуальный, окончательное определение может находиться в будущее или производный класс.Фактическое определение говорит об использовании метода в наиболее производном классе, содержащем определение.

При передаче указателя на Derived_From_Nonvirtual Для Function_B, компилятор проинструктирует функцию использовать метод базового класса, Base_Nonvirtual::some_function().Тот Самый some_function() метод в производном классе - это отдельный, не связанный с базовым классом метод.

Основное различие между virtual и переопределение происходит при полиморфизме.

Ознакомьтесь с C ++ FAQ lite, http://www.parashift.com/c++-faq-lite/.вероятно, это один из лучших ресурсов C ++ для начинающих.в нем содержится подробное описание виртуальных функций и их переопределения.

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

Это скорее продолжение комментариев из этого ответ чем ответ сам по себе.

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

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

struct base {
   virtual void override() { std::cout << "base::override" << std::endl; }
   void not_override() { std::cout << "base::not_override" << std::endl; }
};
struct derived : base {
   void override() { std::cout << "derived::override" << std::endl; }
   void not_override() { std::cout << "derived::not_override" << std::endl; }
};
int main() {
   derived d;
   base & b = d;

   b.override();     // derived::override
   b.not_override(); // base::not_override
   d.not_override(); // derived::not_override
}

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

Другие вопросы

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

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

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

struct base {
   void f() {}
};
struct derived : base {
   void f(int) {}
};
int main() {
   derived d;
   // d.f() // error, derived::f requires an argument, base::f is hidden in this context
}

Как и в случае с переопределяет, d.base::f() вызовет базовую версию не потому, что она отключает полиморфизм - это не так, поскольку метод не объявлен виртуальным, он никогда не будет иметь полиморфного поведения, - а потому, что полная квалификация сообщает компилятору, где находится метод, даже если он был скрыт другим методом в производном классе.

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

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

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

По мере того как вы будете больше изучать C ++, вы обнаружите, что наследование - это не все, чем оно кажется на первый взгляд.Состав и часто является лучшей альтернативой.Получайте удовольствие.

При переходе с Java можно было бы встретить концепцию virtual vs.невиртуальные функции-члены сбивают с толку.Следует помнить, что методы Java соответствуют виртуальным функциям-членам в C ++.

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

Классическим примером является программа paint, в которой базовый класс Shape создается с помощью виртуальной функции draw().Затем каждая из фигур (круг, прямоугольник, треугольник и т.д.) Может быть создана как подкласс, каждый из которых соответствующим образом реализует свою функцию draw(), и основная программа paint может сохранять список фигур, каждая из которых будет выполнять соответствующую функцию draw(), даже если сохранен только указатель на базовый класс Shape.

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

#include <iostream>

class A{
    public:
    virtual void getA() { std::cout << "A in base" << std::endl;};
};

class B : public A {
    public:
    void getA() { std::cout << "A in derived class" << std::endl;}
};

int main(int argc, char** argv)
{
    A a;
    B b;
    a.getA();
    b.getA();

    A* t = new B;
    t->getA();
}

Например:в этой программе t->getA() Печать "A in derived class", но если бы в базовом классе A не было виртуального модификатора, то он выводил бы "A in base".

Надеюсь, это поможет.

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

Однако, если и вертолет, и самолет следуют интерфейсу flyer, чем если бы у вас был объект аэродрома и вы дали ему Флаер, все, что нужно сделать аэродрому, это запросить флаеры для полета.

Например:

Airplace X=Airplane X("boeing 747");
Airfield::takeoff(&X);

Helicopter Y= Helicopter("Comache");
Airfield::takeof(&Y);

void Airfield::takeOff(Flyer * f)
{
     f->fly();
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top