[C++] クラスの関数をオーバーライドする方法
C++でクラスの関数をオーバーライドするには、基底クラスで仮想関数virtual
として宣言された関数を派生クラスで再定義します。
派生クラスで関数をオーバーライドする際には、関数のシグネチャ(引数や戻り値の型)が基底クラスのものと一致している必要があります。
C++11以降では、オーバーライドする関数にoverride
キーワードを付けることで、意図的なオーバーライドであることを明示し、間違いを防ぐことができます。
- オーバーライドの基本
- overrideキーワードの重要性
- 抽象クラスと純粋仮想関数の利用
- 多重継承における注意点
- スマートポインタとの組み合わせ方法
オーバーライドとは何か
オーバーライドとは、C++において基底クラスで定義された仮想関数を、派生クラスで再定義することを指します。
これにより、派生クラスのオブジェクトが基底クラスのポインタや参照を通じて呼び出された場合でも、派生クラスで定義された関数が実行されるようになります。
オーバーライドは、ポリモーフィズムを実現するための重要な機能であり、異なるクラス間での一貫したインターフェースを提供します。
これにより、コードの再利用性や拡張性が向上し、柔軟なプログラミングが可能になります。
C++でのオーバーライドの基本
基底クラスでの仮想関数の宣言
基底クラスでオーバーライドを行うためには、まず仮想関数を宣言する必要があります。
仮想関数は、virtual
キーワードを用いて定義され、派生クラスで再定義されることを前提としています。
以下は、基底クラスでの仮想関数の宣言の例です。
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // 仮想関数の宣言
cout << "Base class show function" << endl;
}
};
派生クラスでの関数の再定義
派生クラスでは、基底クラスで宣言された仮想関数を再定義することができます。
この再定義により、派生クラスのオブジェクトが基底クラスのポインタを通じて呼び出された場合でも、派生クラスの関数が実行されます。
以下は、派生クラスでの関数の再定義の例です。
class Derived : public Base {
public:
void show() override { // 関数の再定義
cout << "Derived class show function" << endl;
}
};
overrideキーワードの使用
C++11以降、override
キーワードを使用することで、関数が基底クラスの仮想関数をオーバーライドしていることを明示的に示すことができます。
これにより、誤って関数のシグネチャを変更してしまった場合にコンパイルエラーが発生し、バグを防ぐことができます。
上記の例でもoverride
を使用しています。
オーバーライド時の関数シグネチャの一致
オーバーライドを行う際には、基底クラスの仮想関数と派生クラスの関数のシグネチャが一致している必要があります。
具体的には、関数名、引数の型、引数の数が同じでなければなりません。
戻り値の型は、基底クラスの関数と同じか、派生クラスの関数が戻り値を変えることはできません。
これにより、正しい関数が呼び出されることが保証されます。
オーバーライドとアクセス修飾子の関係
オーバーライドを行う際には、アクセス修飾子にも注意が必要です。
基底クラスの仮想関数がpublic
またはprotected
として宣言されている場合、派生クラスでのオーバーライドも同様にpublic
またはprotected
でなければなりません。
private
でオーバーライドすることはできず、アクセス修飾子が異なると、オーバーライドとは見なされません。
これにより、クラスの設計が意図した通りに機能することが保証されます。
オーバーライドの具体例
基本的なオーバーライドの例
基本的なオーバーライドの例として、基底クラスAnimal
とその派生クラスDog
を考えます。
Animalクラス
には仮想関数makeSound
があり、Dogクラス
でこの関数をオーバーライドします。
#include <iostream>
using namespace std;
class Animal {
public:
virtual void makeSound() { // 基底クラスの仮想関数
cout << "Animal sound" << endl;
}
};
class Dog : public Animal {
public:
void makeSound() override { // オーバーライド
cout << "Woof!" << endl;
}
};
int main() {
Animal* animal = new Dog(); // 基底クラスのポインタで派生クラスを指す
animal->makeSound(); // "Woof!"が出力される
delete animal;
return 0;
}
Woof!
overrideキーワードを使った例
override
キーワードを使用することで、オーバーライドを明示的に示すことができます。
以下の例では、Shapeクラス
とその派生クラスCircle
でdraw関数
をオーバーライドしています。
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() { // 基底クラスの仮想関数
cout << "Drawing a shape" << endl;
}
};
class Circle : public Shape {
public:
void draw() override { // `override`キーワードを使用
cout << "Drawing a circle" << endl;
}
};
int main() {
Shape* shape = new Circle(); // 基底クラスのポインタで派生クラスを指す
shape->draw(); // "Drawing a circle"が出力される
delete shape;
return 0;
}
Drawing a circle
複数の派生クラスでのオーバーライド
複数の派生クラスで同じ基底クラスの仮想関数をオーバーライドすることも可能です。
以下の例では、Vehicleクラス
を基底クラスとして、Car
とBike
の2つの派生クラスがそれぞれmove関数
をオーバーライドしています。
#include <iostream>
using namespace std;
class Vehicle {
public:
virtual void move() { // 基底クラスの仮想関数
cout << "Vehicle is moving" << endl;
}
};
class Car : public Vehicle {
public:
void move() override { // `Car`クラスでオーバーライド
cout << "Car is driving" << endl;
}
};
class Bike : public Vehicle {
public:
void move() override { // `Bike`クラスでオーバーライド
cout << "Bike is pedaling" << endl;
}
};
int main() {
Vehicle* vehicle1 = new Car(); // `Car`のインスタンス
Vehicle* vehicle2 = new Bike(); // `Bike`のインスタンス
vehicle1->move(); // "Car is driving"が出力される
vehicle2->move(); // "Bike is pedaling"が出力される
delete vehicle1;
delete vehicle2;
return 0;
}
Car is driving
Bike is pedaling
コンストラクタやデストラクタのオーバーライドは可能か?
C++では、コンストラクタやデストラクタをオーバーライドすることはできません。
コンストラクタはクラスのインスタンス化時に呼び出され、デストラクタはインスタンスが破棄される際に呼び出されるため、オーバーライドの概念が適用されません。
ただし、基底クラスのデストラクタを仮想関数として宣言することで、派生クラスのデストラクタが正しく呼び出されるようにすることは可能です。
オーバーライドに関する注意点
オーバーライド時の型の不一致によるエラー
オーバーライドを行う際には、基底クラスの仮想関数と派生クラスの関数のシグネチャが一致している必要があります。
型の不一致があると、コンパイルエラーが発生します。
例えば、基底クラスの関数がint型
の引数を持つ場合、派生クラスの関数も同じ型の引数を持たなければなりません。
以下の例では、型の不一致によるエラーが発生します。
#include <iostream>
using namespace std;
class Base {
public:
virtual void display(int value) { // 基底クラスの仮想関数
cout << "Base value: " << value << endl;
}
};
class Derived : public Base {
public:
void display(double value) { // 型が不一致
cout << "Derived value: " << value << endl;
}
};
int main() {
Base* obj = new Derived();
obj->display(10); // エラー: display(int)が見つからない
delete obj;
return 0;
}
overrideキーワードを使わない場合のリスク
override
キーワードを使用しない場合、基底クラスの仮想関数をオーバーライドするつもりが、誤って異なるシグネチャの関数を定義してしまうことがあります。
この場合、基底クラスの関数はオーバーライドされず、意図しない動作を引き起こす可能性があります。
以下の例では、override
を使わないことで、意図しない動作が発生します。
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // 基底クラスの仮想関数
cout << "Base class show function" << endl;
}
};
class Derived : public Base {
public:
void show(int value) { // オーバーライドではない
cout << "Derived class show function with value: " << value << endl;
}
};
int main() {
Base* obj = new Derived();
obj->show(); // "Base class show function"が出力される
delete obj;
return 0;
}
基底クラスの関数を呼び出す方法(Base::関数名)
派生クラスでオーバーライドした関数内から、基底クラスの関数を呼び出すことができます。
これにより、基底クラスの機能を保持しつつ、派生クラスの特定の処理を追加することが可能です。
以下の例では、Derivedクラス
のshow関数
内から基底クラスのshow関数
を呼び出しています。
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base class show function" << endl;
}
};
class Derived : public Base {
public:
void show() override {
Base::show(); // 基底クラスの関数を呼び出す
cout << "Derived class show function" << endl;
}
};
int main() {
Base* obj = new Derived();
obj->show(); // "Base class show function"と"Derived class show function"が出力される
delete obj;
return 0;
}
Base class show function
Derived class show function
オーバーライドとポリモーフィズムの関係
オーバーライドはポリモーフィズムの実現において重要な役割を果たします。
ポリモーフィズムとは、異なるクラスのオブジェクトが同じインターフェースを持ち、同じメソッドを呼び出すことで異なる動作をすることを指します。
オーバーライドを使用することで、基底クラスのポインタや参照を通じて、派生クラスの関数を呼び出すことができ、柔軟なプログラミングが可能になります。
これにより、コードの再利用性や拡張性が向上し、より効率的な設計が実現されます。
C++11以降のオーバーライドの新機能
overrideキーワードの導入
C++11では、override
キーワードが導入され、派生クラスで基底クラスの仮想関数をオーバーライドする際に使用できるようになりました。
このキーワードを使用することで、関数が基底クラスの仮想関数を正しくオーバーライドしていることを明示的に示すことができます。
もしシグネチャが一致しない場合、コンパイラはエラーを報告します。
以下の例では、override
を使用したオーバーライドの例を示します。
#include <iostream>
using namespace std;
class Base {
public:
virtual void display() {
cout << "Base class display function" << endl;
}
};
class Derived : public Base {
public:
void display() override { // `override`キーワードを使用
cout << "Derived class display function" << endl;
}
};
int main() {
Base* obj = new Derived();
obj->display(); // "Derived class display function"が出力される
delete obj;
return 0;
}
Derived class display function
finalキーワードによるオーバーライドの制限
C++11では、final
キーワードを使用することで、特定の関数がオーバーライドされることを防ぐことができます。
これにより、基底クラスの関数をオーバーライドできないように制限することができます。
以下の例では、Baseクラス
のshow関数
にfinal
を指定し、Derivedクラス
でのオーバーライドを禁止しています。
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() final { // `final`キーワードを使用
cout << "Base class show function" << endl;
}
};
class Derived : public Base {
public:
void show() { // エラー: `show`はオーバーライドできない
cout << "Derived class show function" << endl;
}
};
int main() {
Base* obj = new Derived();
obj->show(); // "Base class show function"が出力される
delete obj;
return 0;
}
noexceptとオーバーライドの関係
C++11以降、関数にnoexcept
指定子を追加することができ、例外を投げないことを明示的に示すことができます。
オーバーライドする際、基底クラスの関数がnoexcept
である場合、派生クラスのオーバーライド関数もnoexcept
でなければなりません。
以下の例では、noexcept
を使用したオーバーライドの例を示します。
#include <iostream>
using namespace std;
class Base {
public:
virtual void process() noexcept { // `noexcept`指定
cout << "Base class process function" << endl;
}
};
class Derived : public Base {
public:
void process() noexcept override { // `noexcept`を維持
cout << "Derived class process function" << endl;
}
};
int main() {
Base* obj = new Derived();
obj->process(); // "Derived class process function"が出力される
delete obj;
return 0;
}
Derived class process function
constexpr関数のオーバーライド
C++11以降、constexpr関数
を定義することができ、コンパイル時に評価される関数を作成できます。
constexpr関数
もオーバーライドすることが可能で、基底クラスのconstexpr関数
を派生クラスでオーバーライドすることができます。
以下の例では、constexpr関数
のオーバーライドを示します。
#include <iostream>
using namespace std;
class Base {
public:
virtual constexpr int calculate(int x) const { // `constexpr`関数
return x + 1;
}
};
class Derived : public Base {
public:
constexpr int calculate(int x) const override { // オーバーライド
return x * 2;
}
};
int main() {
Base* obj = new Derived();
cout << obj->calculate(5) << endl; // "10"が出力される
delete obj;
return 0;
}
10
オーバーライドの応用例
抽象クラスと純粋仮想関数のオーバーライド
抽象クラスは、少なくとも1つの純粋仮想関数を持つクラスであり、インスタンス化することはできません。
派生クラスはこの純粋仮想関数をオーバーライドする必要があります。
以下の例では、Shapeクラス
が抽象クラスであり、draw関数
が純粋仮想関数として定義されています。
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // 純粋仮想関数
};
class Circle : public Shape {
public:
void draw() override { // オーバーライド
cout << "Drawing a circle" << endl;
}
};
int main() {
Shape* shape = new Circle(); // 派生クラスのインスタンス
shape->draw(); // "Drawing a circle"が出力される
delete shape;
return 0;
}
Drawing a circle
インターフェースとしての仮想関数の利用
C++では、仮想関数を使用してインターフェースを定義することができます。
インターフェースは、異なるクラスが同じメソッドを持つことを保証し、ポリモーフィズムを実現します。
以下の例では、Animal
インターフェースを定義し、Dog
とCatクラス
がこのインターフェースを実装しています。
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() = 0; // 純粋仮想関数
};
class Dog : public Animal {
public:
void speak() override { // オーバーライド
cout << "Woof!" << endl;
}
};
class Cat : public Animal {
public:
void speak() override { // オーバーライド
cout << "Meow!" << endl;
}
};
int main() {
Animal* animals[2];
animals[0] = new Dog();
animals[1] = new Cat();
for (int i = 0; i < 2; i++) {
animals[i]->speak(); // 各動物の鳴き声を出力
}
for (int i = 0; i < 2; i++) {
delete animals[i];
}
return 0;
}
Woof!
Meow!
多重継承とオーバーライドの衝突
C++では多重継承が可能ですが、基底クラスに同名の仮想関数が存在する場合、オーバーライドの衝突が発生することがあります。
この場合、どの基底クラスの関数をオーバーライドするかを明示的に指定する必要があります。
以下の例では、Base1
とBase2
の両方にshow関数
があり、Derivedクラス
でのオーバーライドを示しています。
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void show() {
cout << "Base1 show function" << endl;
}
};
class Base2 {
public:
virtual void show() {
cout << "Base2 show function" << endl;
}
};
class Derived : public Base1, public Base2 {
public:
void show() override { // オーバーライド
Base1::show(); // Base1のshowを呼び出す
Base2::show(); // Base2のshowを呼び出す
}
};
int main() {
Derived obj;
obj.show(); // "Base1 show function"と"Base2 show function"が出力される
return 0;
}
Base1 show function
Base2 show function
テンプレートクラスでのオーバーライド
C++では、テンプレートクラスを使用して、型に依存しないクラスを作成することができます。
テンプレートクラス内で仮想関数を定義し、派生クラスでオーバーライドすることも可能です。
以下の例では、テンプレートクラスBase
を定義し、Derivedクラス
でオーバーライドしています。
#include <iostream>
using namespace std;
template <typename T>
class Base {
public:
virtual void display(T value) {
cout << "Base class value: " << value << endl;
}
};
class Derived : public Base<int> { // int型のテンプレートを使用
public:
void display(int value) override { // オーバーライド
cout << "Derived class value: " << value * 2 << endl;
}
};
int main() {
Base<int>* obj = new Derived();
obj->display(5); // "Derived class value: 10"が出力される
delete obj;
return 0;
}
Derived class value: 10
オーバーライドとスマートポインタの組み合わせ
C++では、スマートポインタを使用することで、メモリ管理を簡素化できます。
オーバーライドされた関数を持つクラスをスマートポインタで管理することができます。
以下の例では、std::unique_ptr
を使用して、オーバーライドされた関数を持つクラスのインスタンスを管理しています。
#include <iostream>
#include <memory> // スマートポインタ用
using namespace std;
class Base {
public:
virtual void show() {
cout << "Base class show function" << endl;
}
virtual ~Base() {} // 仮想デストラクタ
};
class Derived : public Base {
public:
void show() override { // オーバーライド
cout << "Derived class show function" << endl;
}
};
int main() {
unique_ptr<Base> obj = make_unique<Derived>(); // スマートポインタで管理
obj->show(); // "Derived class show function"が出力される
return 0;
}
Derived class show function
よくある質問
まとめ
この記事では、C++におけるオーバーライドの基本から具体的な実装例、注意点、応用例まで幅広く解説しました。
オーバーライドは、ポリモーフィズムを実現するための重要な機能であり、クラス間の柔軟なインターフェースを提供することで、コードの再利用性や拡張性を向上させます。
これを踏まえ、実際のプログラミングにおいてオーバーライドを積極的に活用し、より効率的で保守性の高いコードを目指してみてください。