Поиск…


Синтаксис

  • virtual void f ();

  • virtual void g () = 0;

  • // C ++ 11 или новее:

    • virtual void h () override;
    • void i () переопределить;
    • virtual void j () final;
    • void k () final;

замечания

Использование переопределения с виртуальными в C ++ 11 и более поздних версиях

override спецификатора имеет особое значение в C ++ 11, если оно добавлено в конце сигнатуры функции. Это означает, что функция

  • Переопределение функции, присутствующей в базовом классе &
  • Функция базового класса является virtual

Не существует значимости run time выполнения этого спецификатора, как это в основном означает указание для компиляторов

Пример ниже продемонстрирует изменение в поведении с нашим без использования переопределения.

Без override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Y::f() will not override X::f() because it has a different signature,
    // but the compiler will accept the code (and silently ignore Y::f()).
    virtual void f(int a) { std::cout << a << "\n"; }
};

С override :

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // The compiler will alert you to the fact that Y::f() does not
    // actually override anything.
    virtual void f(int a) override { std::cout << a << "\n"; }
};

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

void foo() {
    int override = 1; // OK.
    int virtual = 2;  // Compilation error: keywords can't be used as identifiers.
}

Виртуальные виртуальные функции-члены

С виртуальными функциями-членами:

#include <iostream>

struct X {
    virtual void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
    // Specifying virtual again here is optional
    // because it can be inferred from X::f().
    virtual void f() { std::cout << "Y::f()\n"; } 
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "Y::f()"
}

Без виртуальных функций-членов:

#include <iostream>

struct X {
   void f() { std::cout << "X::f()\n"; }
};

struct Y : X {
   void f() { std::cout << "Y::f()\n"; }
};

void call(X& a) {
    a.f();
}

int main() {
    X x;
    Y y;
    call(x); // outputs "X::f()"
    call(y); // outputs "X::f()"
}

Конечные виртуальные функции

C ++ 11 представил final спецификатор, который запрещает переопределение метода, если он появился в сигнатуре метода:

class Base {
public:
    virtual void foo() {
        std::cout << "Base::Foo\n";
    }
};

class Derived1 : public Base {
public:
    // Overriding Base::foo
    void foo() final {
        std::cout << "Derived1::Foo\n";
    }
};

class Derived2 : public Derived1 {
public:
    // Compilation error: cannot override final method
    virtual void foo() {
        std::cout << "Derived2::Foo\n";
    }
};

Спецификатор final может быть использован только с `виртуального» функции члена и не могут быть применены к функциям невиртуальных членов

Как и в случае с final , существует также опция «переопределить» спецификатора, которая предотвращает переопределение virtual функций в производном классе.

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

class Derived1 : public Base {
public:
    void foo() final override {
        std::cout << "Derived1::Foo\n";
    }
};

Поведение виртуальных функций в конструкторах и деструкторах

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

#include <iostream>
using namespace std;

class base { 
public:
    base() { f("base constructor"); }
    ~base() { f("base destructor"); }

    virtual const char* v() { return "base::v()"; }

    void f(const char* caller) { 
        cout << "When called from " << caller << ", "  << v() << " gets called.\n"; 
    }        
};

class derived : public base {
public:
    derived() { f("derived constructor"); }
    ~derived() { f("derived destructor"); }

    const char* v() override { return "derived::v()"; }

};

int main() {
     derived d;
}

Выход:

Когда вызывается из базового конструктора, вызывается base :: v ().
Когда вызывается из производного конструктора, вызывается метод output :: v ().
Когда вызывается из производного деструктора, вызывается производный :: v ().
При вызове из базового деструктора вызывается base :: v ().

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

Пример:

#include <iostream>
#include <memory>

using namespace std;
class base {
public:
    base()
    {
        std::cout << "foo is " << foo() << std::endl;
    }
    virtual int foo() { return 42; }
};

class derived : public base {
    unique_ptr<int> ptr_;
public:
    derived(int i) : ptr_(new int(i*i)) { }
    // The following cannot be called before derived::derived due to how C++ behaves, 
    // if it was possible... Kaboom!
    int foo() override   { return *ptr_; } 
};

int main() {
    derived d(4);
}

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

Мы также можем указать, что virtual функция является чистой виртуальной (абстрактной), добавляя = 0 к объявлению. Классы с одной или несколькими чистыми виртуальными функциями считаются абстрактными и не могут быть созданы; могут быть созданы только производные классы, которые определяют или наследуют определения для всех чистых виртуальных функций.

struct Abstract {
    virtual void f() = 0;
};

struct Concrete {
    void f() override {}
};

Abstract a; // Error.
Concrete c; // Good.

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

struct DefaultAbstract {
    virtual void f() = 0;
};
void DefaultAbstract::f() {}

struct WhyWouldWeDoThis : DefaultAbstract {
    void f() override { DefaultAbstract::f(); }
};

Есть несколько причин, почему мы могли бы сделать это:

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

      struct Interface {
          virtual ~Interface() = 0;
      };
      Interface::~Interface() = default;
    
      struct Implementation : Interface {};
      // ~Implementation() is automatically defined by the compiler if not explicitly
      //  specified, meeting the "must be defined before instantiation" requirement.
    
  • Если большинство или все реализации чистой виртуальной функции будут содержать повторяющийся код, этот код вместо этого может быть перенесен в версию базового класса, что упростит работу кода.

      class SharedBase {
          State my_state;
          std::unique_ptr<Helper> my_helper;
          // ...
    
        public:
          virtual void config(const Context& cont) = 0;
          // ...
      };
      /* virtual */ void SharedBase::config(const Context& cont) {
          my_helper = new Helper(my_state, cont.relevant_field);
          do_this();
          and_that();
      }
    
      class OneImplementation : public SharedBase {
          int i;
          // ...
    
        public:
          void config(const Context& cont) override;
          // ...
      };
      void OneImplementation::config(const Context& cont) /* override */ {
          my_state = { cont.some_field, cont.another_field, i };
          SharedBase::config(cont);
          my_unique_setup();
      };
    
      // And so on, for other classes derived from SharedBase.
    


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow