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

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

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

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

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

Ответы

Ответ 1

АВТОРЕФЕРАТ

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

ЧАСТЬ ZERO

Метод класса называется virtual тогда и только тогда, когда он объявлен таким образом.

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(); }
};

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

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 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();
}

Ответ 2

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

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

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

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

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

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

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

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

Ответ 3

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

Базовая установка

В С++ любой производный класс может быть передан функции, требующей объекта базового класса. (См. Также Slicing и 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()
};

struct Derived_From_Nonvirtual : public Base_Nonvirtual { void some_function(); }

В соответствии с языком С++ я могу передать указатель на 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 и переопределением происходит при полиморфизме.

Ответ 4

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

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

Ответ 5

Это больше соответствует комментариям из этого , чем сам ответ.

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() будет вызывать базовую версию, а не потому, что она отключает полиморфизм - это не так, поскольку метод не объявлен виртуальным, он никогда не будет иметь полиморфного поведения, но поскольку полная квалификация сообщает компилятору где метод, даже если он был скрыт другим методом в производном классе.

Ответ 6

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

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

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

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

#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".

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

Ответ 10

Вертолеты и самолеты летают, но они делают это по-разному - они оба являются экземплярами какого-то гипотетического объекта 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();
}

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