目次
低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。
工作HardwareHubからのお知らせ
C++にはJavaなどと異なりインタフェースという機能が存在しません。C++では純粋仮想関数と仮想デストラクタしかメンバをもたないクラスをインタフェースとして利用します。その際、多重継承や仮想継承の知識が必要になります。
多重継承
#include <iostream>
using namespace std;
class MyClass {
public:
virtual ~MyClass() {} // 仮想デストラクタ (インラインメンバ関数として)
virtual void Show() = 0;
virtual void SShow() = 0;
void Set(int intval) {
m_intval = intval;
}
protected:
int m_intval;
};
class MySubClassA :
public MyClass
{
public:
virtual ~MySubClassA() {}
virtual void Show() {
cout << "MySubClassA::Show(" << m_intval << ")" << endl;
}
virtual void SShow() {
cout << "MySubClassA::SShow" << endl;
}
};
class MySubClassB :
public MyClass
{
public:
virtual ~MySubClassB() {}
virtual void Show() {
cout << "MySubClassB::Show(" << m_intval << ")" << endl;
}
virtual void SShow() = 0;
};
class MySubSubClass : // 多重継承。MySubClassA, MySubClassB を介して
public MySubClassA, // MyClass を二つ継承している。MyClass の実体が二つ。
public MySubClassB
{
public:
virtual void SShow() {
cout << "MySubSubClass::Show("
<< MySubClassA::m_intval << ","
<< MySubClassB::m_intval << ")" << endl;
}
};
int main() {
MySubSubClass ss;
MySubClassA& sA = ss; // アップキャスト
MySubClassB& sB = ss;
MyClass& bA = static_cast<MySubClassA&>(ss); // アップキャスト (二段飛び)
MyClass& bB = static_cast<MySubClassB&>(ss); // (曖昧さ回避のため経由する一段目にまずキャスト)
// 実体は二つあるため別々の値を設定可能
sA.Set(1); // 「MySubClassA経由のMyClass」
sB.Set(-1); // 「MySubClassB経由のMyClass」
sA.Show(); //=> MySubClassA::Show(1)
sB.Show(); //=> MySubClassB::Show(-1)
bA.Show(); //=> MySubClassA::Show(1)
bB.Show(); //=> MySubClassB::Show(-1)
// SubSubで実装すると二つの実体の両方がオーバライドされる
ss.SShow(); //=> MySubSubClass::Show(1,-1)
sA.SShow(); //=> MySubSubClass::Show(1,-1)
sB.SShow(); //=> MySubSubClass::Show(1,-1)
bA.SShow(); //=> MySubSubClass::Show(1,-1)
bB.SShow(); //=> MySubSubClass::Show(1,-1)
return 0;
}
仮想継承
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int intval) : m_intval(intval) {}
virtual ~MyClass() {}
virtual void Show() = 0;
protected:
int m_intval;
};
class MySubClassA :
virtual public MyClass // 仮想継承
{
public:
MySubClassA() : MyClass(1) {} // 継承における引数のあるコンストラクタ
virtual ~MySubClassA() {}
virtual void Show() {
cout << m_intval << flush;
}
};
class MySubClassB :
virtual public MyClass // 仮想継承
{
public:
MySubClassB() : MyClass(-1) {}
virtual ~MySubClassB() {}
};
class MySubSubClass :
public MySubClassA, // 多重継承
public MySubClassB // (MyClassの実体は共有され一つ。ダイヤモンド継承)
{
public:
MySubSubClass() : MyClass(0) {} // MySubClassA, MySubClassB のどちらで MyClass()
// を実行したらよいか曖昧なため MySubSubClass で実行。仮想継承のはまりどころです。
virtual void Show() { // 仮に MySubClassA, MySubClassB の両方で Show を実装したと
// すると一つの MyClass に対して Show の実体が二つになりエラーです。
// MySubSubClass で Show を実装するとエラー回避できます。
cout << "MySubSubClass::Show(" << flush;
MySubClassA::Show();
cout << "," << MySubClassB::m_intval << ")" << endl;
}
};
int main() {
MySubSubClass ss;
MySubClassA& sA = ss;
MySubClassB& sB = ss;
MyClass& b = ss; // 実体は一つであるため直接二段飛びのアップキャストが可能
// sA経由でもsB経由でも同じ実体
ss.Show(); //=> MySubSubClass::Show(0,0)
sA.Show(); //=> MySubSubClass::Show(0,0)
sB.Show(); //=> MySubSubClass::Show(0,0)
b.Show(); //=> MySubSubClass::Show(0,0)
// こうすると、MyClassのコンストラクタの引数が異なることに注目
MySubClassA _sA;
// MySubClassB _sB; // MySubClassBは抽象クラスなためエラー (Show が未実装)
_sA.Show(); cout << endl; //=> 1 (← 0でないことがポイントですね)
return 0;
}
インタフェースクラス
純粋仮想関数と仮想デストラクタしかメンバをもたないクラスをインタフェースとして利用します。インタフェースクラスを仮想継承しているクラスには所定のメンバ関数が実装されていることが保証されます。
#include <iostream>
using namespace std;
class IMyClass { // インタフェースクラス
public:
virtual ~IMyClass() {}
virtual void Show() const = 0;
};
class IMySubClassA : // インタフェースクラス
virtual public IMyClass
{
public:
virtual ~IMySubClassA() {}
virtual void ShowA() const = 0;
};
class IMySubClassB : // インタフェースクラス
virtual public IMyClass
{
public:
virtual ~IMySubClassB() {}
virtual void ShowB() const = 0;
};
class MyClass :
virtual public IMySubClassA, // 仮想継承でインタフェースクラスを利用
virtual public IMySubClassB // (実体が一つであることを保証できるため)
{
public:
virtual void Show() const {
cout << "Show" << endl;
}
virtual void ShowA() const {
cout << "ShowA" << endl;
}
virtual void ShowB() const {
cout << "ShowB" << endl;
}
};
void MyFunc(const IMyClass& obj, // アップキャスト
const IMySubClassA& objA,
const IMySubClassB& objB) {
objA.ShowA(); //=> ShowA
objB.ShowB(); //=> ShowB
// インタフェース間の継承関係のため
// ISub は IBase の関数を知っている
obj.Show(); //=> Show
objA.Show(); //=> Show
objB.Show(); //=> Show
}
int main() {
MyClass obj;
MyFunc(obj, obj, obj);
return 0;
}
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...