目次
低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。
サンプルコード
#include <iostream>
using namespace std;
class MyClass {
public: // 外部からも派生クラスからも見える
int Get() const;
private: // 外部からも派生クラスからも見えない
protected: // 外部からは見えないが派生クラスからは見える
int m_intval;
};
class MySubClass :
public MyClass // 継承
// アクセス指定子 (省略時はprivateです。大抵の場合publicを選んでおけばよいです):
// - public: 親のpublic,protected,privateをそのまま引き継ぐ
// - private: 親のpublic,protectedをprivateにして引き継ぐ
// - protected: 親のpublicをprotectedにして引き継ぐ
// ↑要するに厳しい条件を採用して引き継ぎます。ちなみに、protected,privateで継承するとアップキャストを禁止できます。
{
public:
MySubClass(int intval);
};
int MyClass::Get() const {
return m_intval;
}
MySubClass::MySubClass(int intval) {
m_intval = intval;
}
int main() {
MySubClass obj(0);
cout << obj.Get() << endl;
/* アップキャスト (upcast) */
/* - 継承木の上 (up) へのキャスト */
/* - 参照先の実体は派生クラス */
/* - 参照を用いて実行可能なのは親クラスができることのみ */
const MyClass& ref = obj; // アップキャストに関してconstは任意
cout << ref.Get() << endl;
const MyClass* ptr = &obj;
cout << ptr->Get() << endl;
return 0;
}
オーバーライド (仮想関数とアップキャスト)
アップキャストした参照を用いると、派生クラスでオーバーライドされたメンバ関数を実行したつもりであっても基底クラスのものが実行されます。派生クラスのメンバ関数を実行させるためには virtual を基底クラスのメンバ関数に付与しておく必要があります。前者の挙動は意図したものではない場合が多いため「オーバーライドするメンバ関数には基底クラスと派生クラスともに virtual を付与」するのが通常の作法です。
#include <iostream>
using namespace std;
class MyClass {
public:
virtual void Say(); // 仮想関数
// virtual void Say() = 0; // 純粋仮想関数 (MyClassは抽象クラスとなり実体を持てなくなる) (*1)
};
class MySubClass :
public MyClass
{
public:
// 「必須ではない」が分かりやすさのために
// virtual を付与するとよい。
virtual void Say();
};
// void MyClass::Say() { // ← 使用予定がなければ実体を実装する必要はない (*1)
// cout << "MyClass::Say" << endl;
// }
void MySubClass::Say() {
cout << "MySubClass::Say" << endl;
}
int main() {
// MyClass obj; // (*1)
MySubClass objSub;
MyClass& objSubRef = objSub;
// obj.Say(); //=> MyClass::Say // (*1)
objSub.Say(); //=> MySubClass::Say
objSubRef.Say(); //=> MySubClass::Say
return 0;
}
以下のようにアップキャストした参照で直接的に実行する場合でなくても、派生クラスでオーバーライドしたメンバ関数が使用されます。
#include <iostream>
using namespace std;
class MyClass {
public:
void Say();
protected: // 派生クラスで見える必要はあるが外部から実行予定はない
virtual void SayBase(); // 仮想関数
};
class MySubClass :
public MyClass
{
protected:
virtual void SayBase();
};
void MyClass::Say() {
SayBase();
}
void MyClass::SayBase() {
cout << "MyClass::SayBase" << endl;
}
void MySubClass::SayBase() {
cout << "MySubClass::SayBase" << endl;
}
int main() {
MyClass obj;
MySubClass objSub;
MyClass& objSubRef = objSub;
obj.Say(); //=> MyClass::SayBase
objSub.Say(); //=> MySubClass::SayBase
objSubRef.Say(); //=> MySubClass::SayBase
return 0;
}
更に、オーバーライドした関数内で、基底クラスの関数を呼び出すこともできます。
#include <iostream>
using namespace std;
class MyClass {
public:
virtual void Say(); // 仮想関数
};
class MySubClass :
public MyClass
{
public:
virtual void Say();
};
void MyClass::Say() {
cout << "MyClass::Say" << endl;
}
void MySubClass::Say() {
MyClass::Say();
cout << "MySubClass::Say" << endl;
}
int main() {
MySubClass obj;
obj.Say(); //=> MyClass::Say
// MySubClass::Say
return 0;
}
オーバーロードのある仮想関数をオーバーライド
引数の型に応じてオーバーロードがなされているメンバ仮想関数を派生クラスでオーバーライドする際には、すべてがオーバーライドされてしまいます。これを避けるためには using を使用します。
#include <iostream>
using namespace std;
class MyClass {
public:
virtual ~MyClass() {}
public:
virtual void Show(int intval) {
cout << "MyClass:Show(" << intval << ")" << endl;
}
virtual void Show(char chval) {
cout << "MyClass:Show(" << chval << ")" << endl;
}
};
class MySubClass :
public MyClass
{
public:
// Show(int) のオーバライド時に Show(char) が消える
virtual void Show(int intval) {
cout << "MySubClass:Show(" << intval << ")" << endl;
}
// 以下の構文で Show(char) を復旧できる
using MyClass::Show;
// 参考: private と併用して Show(*) を使用禁止にできる
// private:
// using MyClass::Show;
};
int main() {
MySubClass obj;
obj.Show(97); //=> MySubClass:Show(97)
obj.Show('a'); //=> MyClass:Show(a)
return 0;
}
継承における引数のあるコンストラクタ
継承すると
- 基底クラスのコンストラクタ → 派生クラスのコンストラクタ
- 派生クラスのデストラクタ → 基底クラスのデストラクタ
という順番で処理が行われます。このうち基底クラスのコンストラクタについて、引数がある場合は以下のように明示する必要があることに注意しましょう。
またこれは非常に重要なことですが、基底クラスのコンストラクタで (純) 仮想関数を実行すると、仮想関数にも関わらず基底クラスのものを実行しようとします。当然のことながら、純仮想関数の場合は実装がないためエラーになります。これは、上記順番の通り派生クラスのコンストラクタが実行前であり、派生クラスでオーバーライドしたものを実行できないためです。同様に、基底クラスのデストラクタで (純) 仮想関数を実行した場合にも、派生クラスはすでにデストラクタで存在がなくなってしまっているため、基底クラスのものを実行しようとします。
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int intval);
private:
int m_intval;
};
class MySubClass :
public MyClass
{
public:
MySubClass(int intval);
private:
int m_intval_sub;
};
MyClass::MyClass(int intval) :
m_intval(intval) // 基本型intにも実はコンストラクタがあり、それを利用して初期化できます。
{
// m_intval = intval; // と代入するよりもシンプルです。
}
MySubClass::MySubClass(int intval) :
MyClass(intval), // 基底クラスのコンストラクタに引数が不要な場合は省略できます。
m_intval_sub(intval) // 記載の順序によらず、基底クラスのコンストラクタが優先実行されます。
{
// 何らかの処理
}
int main() {
MySubClass obj(0);
return 0;
}
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- ダウンキャスト (C++をもう一度)実行時型情報 RTTI #include <iostream> #include <typeinfo> using namespace std; class MyClass { public: virtual ~MyClass() {} // typeid で正しい RTTI // (RunTime Type Information; 実行時型情報) ...
- 競技プログラミングの基本処理チートシート (C++)限られた時間の中で問題を解くために必要となる、競技プログラミングにおける基本的な処理のチートシートです。競プロにおけるメジャー言語 C++ を利用します。その際 C++11 の機能は利用せず C++03 の機能の範囲内で記述します。 頻度高く定期的に開催されるコンテスト AtCoder Codeforces main.cpp #include <iostream>
- 構造体と列挙体 (C++をもう一度)構造体 #include <iostream> using namespace std; struct MyStruct { char charval; int intval; }; void Show(MyStruct* obj) { cout << obj->intval << endl; } int main() { ...
- Valgrind による C/C++ メモリリーク検出JVM メモリリークでは JDK の jstat や jmap で原因を調査できます。C/C++ では valgrind の Memcheck ツールが利用できます。valgrind には複数のツールが含まれており既定のツールが Memcheck です。他のツールを利用する場合は --tool オプションで指定します。 [簡単な利用例](h
- クラスの基本/初期化 (C++をもう一度)構造体のように初期化する (非推奨) #include <iostream> using namespace std; const int MAX_STR = 16; class MyClass { public: int m_integer; char m_str[MAX_STR + 1]; void Show(); }; void MyClass::Show...