サーチ…
構文
virtual void f();
仮想ボイドg()= 0;
// C ++ 11以降:
- 仮想ボイドh()オーバーライド。
- void i()オーバーライド。
- 仮想void j()final;
- void k()final;
備考
- 非静的でテンプレートでないメンバ関数のみが
virtual
なりvirtual
。 - C ++ 11以降を使用
override
場合は、基本クラスから仮想メンバー関数をオーバーライドするときにオーバーライドを使用することをお勧めしoverride
。 - 多態性基底クラスは、派生したオブジェクトを基底クラスへのポインタを通して削除できるようにする仮想デストラクタを持つことがよくあります 。デストラクタが仮想でない場合、このような操作は未定義の動作 [expr.delete]§5.3.5/ 3につながります。
C ++ 11以降でのオーバーライドの使用
C ++ 11以降では、関数シグネチャの最後に追加された場合、指定子のoverride
は特別な意味を持ちoverride
。これは、関数が
- ベースクラス&
- 基本クラスの関数は
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
は `virtual 'メンバ関数でのみ使用でき、非仮想メンバ関数には適用できません
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()が呼び出されます。
派生コンストラクタから呼び出されると、derived :: 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
を追加することで指定することもできます。 1つ以上の純粋仮想関数を持つクラスは、抽象クラスとみなされ、インスタンス化することはできません。すべての純粋仮想関数を定義または継承する派生クラスのみをインスタンス化できます。
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.