はかせのラボ

私の頭の中を書いていく雑記ブログです

C++ ダックタイピングとインターフェース

あいさつ

どうも、はかせです。
今回はダックタイピングについてです。

ダックタイピングとは

特定メソッドを持っているか否かで判定しもっていたら呼ぶ機能です。

どんな型でどういう文脈で呼ばれていようと
読んでいるメソッド名さえ合っていれば呼ぶことが出来るという
強力な機能です。

使用例

class Jotaro
{
public:
	void Ruch()
	{
		std::cout << "オラオラオラオラ!" << std::endl;
	}
};

class Dio
{
public:
	void Ruch()
	{
		std::cout << "無駄無駄無駄無駄!" << std::endl;
	}
};

int main(int argc, char const* argv[])
{
#if _DEBUG
	_CrtSetDbgFlag
	(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
#endif
	auto jotaro = std::make_unique<Jotaro>();
	auto dio = std::make_unique<Dio>();

	//それぞれのクラスのポインタを渡し
	//Ruchを呼び出す
	Ruch(jotaro.get());
	Ruch(dio.get());
	getchar();
}

JotaroクラスとDioクラスはRuchメソッドを持っています。
ただそれだけです。

特定クラスを継承しているわけでも、
何らかの方法で持っていることを保証しているわけではありません。
ですがこれは実行できます。
f:id:hakase0274:20190102231940p:plain

ではRuchメソッドを持っていないクラスを渡すとどうなるんでしょう?

class TestClass {};

int main(int argc, char const* argv[])
{
#if _DEBUG
	_CrtSetDbgFlag
	(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
#endif
	auto test = std::make_unique<TestClass>();
	Ruch(test.get());
	getchar();
}

このタイミングではエラーも何もでません。
実行してみましょう。
f:id:hakase0274:20190102232447p:plain
コンパイルエラーになりました。

実行時エラーではありません。
コンパイルエラーです。
(重要なことなので2回言いました)

実行時エラーにならないので
プログラムを作って動かしたらなんかわかんないけどエラー出て
プログラムが止まったという悲しい事件が起きません。

もう少し親切に教えてほしい

実際問題何もせずこのまま使っても問題はありません。
ただコンパイルエラーになるとしても
コードを書いてる段階で赤ラインとか出てほしいですよね?
(少なくとも私は出てほしいです)

そこで抽象クラスを使います。
抽象クラスには純粋仮想関数というものを作ることが出来ます。
これは継承先で関数の実装を実装を強制させる機能です。

//継承したクラスにRuchメソッドの実装を強制させる
struct IRuch 
{
	virtual void Ruch() = 0;
	virtual ~IRuch();
};

そしてダックタイピングするメソッドではこの抽象クラスを引数に取ります。

void Ruch(IRuch* rucher)
{
	rucher->Ruch();
}

こうすることでRuchメソッドを持ったクラス以外はRuchメソッドの引数に渡すことはできなくなりました。
渡そうとするとこうなります。
f:id:hakase0274:20190102233657p:plain

無事エディタ上で赤ラインエラーが出るようになりました。

ちなみに今回使った純粋仮想関数と仮想デストラクタのみで
構築されたクラスのことをインターフェースと言ったりします。
(C#Javaでは機能としてinterfaceが用意されています)

あとがき

今回はダックタイピングとインターフェースの紹介でした。
この仕組みを使うと不要なミスを減らしながら
多様性の実現を容易にできます。

インターフェースに関しては必要性はありませんが、
ヒューマンエラーを減らすうえでは有用だと思います。

それでは今回はこの辺でノシ