Виртуальный / чисто виртуальный объяснил

347

Что именно это означает, если функция определена как виртуальная, и это то же самое, что и чисто виртуальная?

Джастин
источник

Ответы:

340

Из Виртуальной функции Википедии ...

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

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

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

в то время как..

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

Когда существует чисто виртуальный метод, класс является «абстрактным» и не может быть создан самостоятельно. Вместо этого должен использоваться производный класс, который реализует чисто виртуальный метод (ы). Чисто виртуальный вообще не определен в базовом классе, поэтому производный класс должен его определять, или этот производный класс также является абстрактным и не может быть создан. Только класс, который не имеет абстрактных методов, может быть создан.

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

Диего Диас
источник
10
Итак ... чисто виртуальное ключевое слово или просто термин, который используется?
Джастин
198
виртуальная пустота Функция () = 0; это чисто виртуальный. «= 0» означает чистоту.
Гоз
8
Джастин, «чисто виртуальный» - это просто термин (не ключевое слово, см. Мой ответ ниже), используемый для обозначения «эта функция не может быть реализована базовым классом. Как сказал Гоз, добавляя« = 0 »в конец виртуального функция делает его «чистым»
Ник Хаддад
14
Я полагаю, что Страуструп сказал, что он хотел добавить pureключевое слово, но Bell Labs собиралась выпустить крупную версию C ++, и его менеджер не допустил бы этого на этой поздней стадии. Добавление ключевых слов это большое дело.
кварк
14
Это не хороший ответ. Любой метод может быть переопределен, не только виртуальный. Смотрите мой ответ для более подробной информации.
Асик
212

Я хотел бы прокомментировать определение виртуального Википедии, как повторяется здесь. [На момент написания этого ответа] Википедия определила виртуальный метод как метод, который можно переопределить в подклассах. [К счастью, с тех пор Википедия была отредактирована, и теперь она объясняет это правильно.] Это неверно: любой метод, не только виртуальный, может быть переопределен в подклассах. Что делает виртуальный, так это дает вам полиморфизм, то есть способность выбирать во время выполнения наиболее производное переопределение метода .

Рассмотрим следующий код:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

Каков вывод этой программы?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

Производный переопределяет каждый метод Base: не только виртуальный, но и не виртуальный.

Мы видим, что когда у вас есть Base-pointer-to-Derived (bDerived), вызов NonVirtual вызывает реализацию базового класса. Это решается во время компиляции: компилятор видит, что bDerived - это Base *, что NonVirtual не является виртуальным, поэтому он выполняет разрешение для класса Base.

Однако вызов Virtual вызывает реализацию класса Derived. Из-за ключевого слова virtual выбор метода происходит во время выполнения , а не во время компиляции. Во время компиляции происходит то, что компилятор видит, что это Base *, и что он вызывает виртуальный метод, поэтому он вставляет вызов vtable вместо класса Base. Этот vtable создается во время выполнения, следовательно, разрешение во время выполнения переопределяется по большинству производных.

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

Asik
источник
6
Тот факт, что статья в Википедии (которую я ни в коем случае не защищаю) определяет виртуальный метод «как метод, который может быть переопределен в подклассах», не исключает возможности объявления других, не виртуальных, методов с тем же именем. Это известно как перегрузка.
26
Определение, тем не менее, неверно. Метод, который может быть переопределен в производном классе, не является виртуальным по определению; возможность переопределения метода не имеет отношения к определению «виртуальный». Кроме того, «перегрузка» обычно означает наличие нескольких методов с одинаковым именем и типом возврата, но разными аргументами в одном классе; это очень отличается от «переопределения», которое подразумевает точно такую ​​же сигнатуру, но в производном классе. Когда это делается не полиморфно (не виртуально), это часто называют «сокрытием».
Асик
5
Это должен быть принятый ответ. Эта конкретная статья в Википедии, на которую я потрачу время, чтобы ссылаться здесь, поскольку никто другой по этому вопросу не сделал этого , является полным мусором. +1, сэр
josaphatv
2
СЕЙЧАС это имеет смысл. Спасибо, сэр, за правильное объяснение того, что любой метод может быть переопределен производными классами, и изменение заключается в том, как компилятор будет вести себя, выбирая, какая функция вызывается в разных ситуациях.
Doodad
3
Может быть полезно добавить a Derived*с теми же вызовами функций, чтобы увести точку домой. В противном случае отличный ответ
Джефф Джонс
114

Ключевое слово virtual дает C ++ возможность поддерживать полиморфизм. Когда у вас есть указатель на объект некоторого класса, такой как:

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

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

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

Если мы сделаем это:

Duck d;
SomeFunction(&d);

это вывело бы '2'. Если мы сделаем это:

Horse h;
SomeFunction(&h);

это вывело бы '4'. Мы не можем сделать это:

Animal a;
SomeFunction(&a);

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

Чистые виртуальные функции в основном используются для определения:

а) абстрактные классы

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

б) интерфейсы

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

JBRWilkinson
источник
В вашем примере вы не можете сделать № 4, потому что вы не предоставили реализацию чисто виртуального метода. Это не строго, потому что метод является чисто виртуальным.
iheanyi
@iheanyi Вы не можете предоставить реализацию чисто виртуального метода в базовом классе. Следовательно, случай № 4 все еще является ошибкой.
прасад
32

В классе C ++, virtual - это ключевое слово, которое обозначает, что метод может быть переопределен (т.е. реализован) подклассом. Например:

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

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

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

Термин « чисто виртуальный» относится к виртуальным функциям, которые должны быть реализованы подклассом и не были реализованы базовым классом. Вы определяете метод как чисто виртуальный, используя ключевое слово virtual и добавляя = 0 в конце объявления метода.

Итак, если вы хотите сделать Shape :: initShape чисто виртуальным, вы должны сделать следующее:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

Добавляя чистый виртуальный метод в ваш класс, вы делаете класс абстрактным базовым классом, который очень удобен для отделения интерфейсов от реализации.

Ник Хаддад
источник
1
Относительно «виртуальных функций, которые должны быть реализованы подклассом» - это не совсем верно, но подкласс также абстрактен, если это не так. И абстрактные классы не могут быть созданы. Кроме того, «не может быть реализовано базовым классом» кажется вводящим в заблуждение; Я бы предположил, что «не было» было бы лучше, поскольку нет ограничений на модификации кода для добавления реализации в базовый класс.
NVRAM
2
И «функция getName не может быть реализована подклассом» не совсем правильно. Подклассы могут реализовывать метод (с одной и той же или другой сигнатурой), но эта реализация не будет ПЕРЕДАТЬ метод. Вы можете реализовать Circle как подкласс и реализовать "std :: string Circle :: getName ()" - тогда вы можете вызвать любой метод для экземпляра Circle. Но если использовать указатель Shape или ссылку, компилятор вызовет Shape :: getName ().
NVRAM
1
Хорошие очки на обоих фронтах. Я старался держаться подальше от обсуждения особых случаев для этого примера, я изменю ответ, чтобы он был более щадящим. Спасибо!
Ник Хаддад
@NickHaddad Старый поток, но интересно, почему вы назвали свою переменную m_name. Что m_значит?
TqN
1
@Tqn при условии, что NickHaddad следовал соглашениям, m_name - соглашение об именах, обычно называемое венгерской нотацией. M указывает член структуры / класса, целое число.
Кеткомп
16

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

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

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

Согласно комментариям, неудача компиляции зависит от компилятора. По крайней мере, в GCC 4.3.3 он не будет компилироваться:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

Вывод:

$ g++ -c virt.cpp 
virt.cpp: In function int main()’:
virt.cpp:8: error: cannot declare variable a to be of abstract type A
virt.cpp:1: note:   because the following virtual functions are pure within A’:
virt.cpp:3: note:   virtual void A::Hello()
Джон Милликин
источник
он должен быть переопределен, если вы хотите создать экземпляр класса. Если вы не создадите ни одного экземпляра, код будет хорошо скомпилирован.
Глен
1
Компиляция не подведет. Если нет реализации (чистого) виртуального метода, то этот класс / объект не может быть создан. Возможно, это не ССЫЛКА, но она скомпилируется.
Тим
@Glen, @tim: на каком компиляторе? Когда я пытаюсь скомпилировать программу, которая создает абстрактный класс, она не компилируется.
Джон Милликин
@John Компиляция потерпит неудачу, только если вы попытаетесь создать экземпляр класса, который содержит PVF. Конечно, вы можете создать указатель или ссылочные значения для таких классов.
5
Кроме того, Джон, следующее не совсем верно: «« Чистый виртуальный »означает, что это виртуальный метод без реализации». Чистые виртуальные методы могут иметь реализации. Но вы не можете вызывать их напрямую: вы должны переопределить и использовать реализацию базового класса внутри подкласса. Это позволяет вам предоставить часть реализации по умолчанию. Это не обычная техника, хотя.
кварк
9

Как работает виртуальное ключевое слово?

Предположим, что человек является базовым классом, индиец происходит от человека.

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

Объявление do_work () как виртуального просто означает, что do_work () для вызова будет определено ТОЛЬКО во время выполнения.

Предположим, я делаю,

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

Если virtual не используется, то же самое статически определяется или статически связывается компилятором, в зависимости от того, какой объект вызывается. Так что если объект Man вызывает do_work (), то do_work () Man называется ДАЖЕ, ЧТО ЭТО УКАЗЫВАЕТ НА ИНДИЙСКИЙ ОБЪЕКТ

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

Кажется, есть еще один вводящий в заблуждение комментарий, который гласит:

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

ЭТО НЕ ВЕРНО! Чисто виртуальные функции также могут иметь тело и могут быть реализованы! Правда в том, что чисто виртуальную функцию абстрактного класса можно вызывать статически! Два очень хороших автора - Бьярн Страуструп и Стэн Липпман ... потому что они написали язык.

МакМердо
источник
2
К сожалению, как только за ответ начнется голосование, все остальные будут проигнорированы. Даже если бы они могли быть лучше.
LtWorf
3

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

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

rashedcs
источник
2

Simula, C ++ и C #, которые по умолчанию используют статическое связывание методов, программист может указать, что конкретные методы должны использовать динамическое связывание, пометив их как виртуальные. Динамическое связывание методов является центральным в объектно-ориентированном программировании.

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

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

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

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

PJT
источник
1

Виртуальные методы МОГУТ быть переопределены производными классами, но нуждаются в реализации в базовом классе (тот, который будет переопределен)

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

Virtual соответствует стандартному поведению Java, когда производный класс переопределяет метод базового класса.

Чисто виртуальные методы соответствуют поведению абстрактных методов внутри абстрактных классов. И класс, который содержит только чистые виртуальные методы и константы, будет cpp-pendant для интерфейса.

johannes_lalala
источник
0

Чистая виртуальная функция

попробуй этот код

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

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

Виртуальная функция

попробуй другой код

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

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

Тунвир Рахман Тушер
источник
Ха-ха, мне потребовались долгие 30 секунд, чтобы понять, что здесь не так ... HelloW :)
hans
0

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

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

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

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

  • Виртуальные функции объявлены и определены в обычном классе.

  • Чистая виртуальная функция должна быть объявлена ​​с окончанием "= 0", и она может быть объявлена ​​только в абстрактном классе.

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

Сохаил xIN3N
источник
То же самое, что и для @rashedcs: действительно, у чистой виртуальной функции может быть свое определение ...
Jarek C