見出し画像

C++ 再入門 その18 - 継承と親子間の型キャスト

さて継承については、まだまだ続きます。親となるクラスを元に継承を使うことで、子クラスの定義が出来ることはわかったのかなと思います。さて、子クラスのインスタンス変数を作った場合、子クラスには親クラスの情報が含まれているので、親クラスの変数のように振る舞うこともできます。つまり子クラスの変数を親クラスに「型変換」することが出来るのです。このように子供から親への変換をアップキャストと呼びますが、これは「安全」であるとされています。ここで安全という用語が出てくるのが不思議に思えるかもしれませんが、何が安全で何が安全ではないのかが今回の話です。

まず、子クラスから親クラスに型変換してみましょう。

#include <iostream>
using namespace std;

class Parent {
public:
  void MParent() { cout << ParentMethod" << endl; }
};

class Child : public Parent {
public:
  void MChild() { cout << "ChildMethod" << endl; }
};

int main() {
  Child c;
  c.MParent();
  c.MChild();

  Parent& p = c;
  p.MParent();

  Child *cp = new Child;
  cp->MParent();
  cp->MChild();

  Parent *pp = cp;
  pp->MParent();

  delete cp;
  return 0;
}

ParentMethod
ChildMethod
ParentMethod
ParentMethod
ChildMethod
ParentMethod

実行結果

ここまでは大丈夫ですね?子クラスには親クラスの情報を含んでいるので何も問題はありません。但しキャストした参照やポインタからは、もう子クラスのメソッドを使えません。親クラスのポインタへ子クラスのポインタを代入すれば、何も書かなくても暗黙の変換が行われます。もちろんC言語のようにカッコで囲って型名を書くことも出来ますし、その方が型変換が行われていることが読み取れるので親切です。さらにC++ではC言語のようなカッコで囲った型変換をもう少し丁寧にstatic_castというテンプレートを使って明示することも出来ます。

Parent *pp = cp; // 暗黙の型変換
Parent *pp =(Parent*)cp; // C流儀の型変換
Parent *pp = static_cast<Parent*>(cp); // C++での静的な型変換

これらはいずれも同じ意味

static_castはCの型変換と同じで指示されたとおりに変換してしまいます(コンパイル時に変換するコードを埋め込んでしまう)。この変換が安全なのかどうかはコードを書く人に委ねられています。まあ、それはCと同じ話なわけです。

さて、ここで復習も兼ねて親クラスと同じ名前のメソッドを子クラスでも定義してみましょう。

#include <iostream>
using namespace std;

class Parent {
public:
  void MParent() { cout << "ParentMethod" << endl; }
};

class Child : public Parent {
public:
  void MParent() { cout << "OverridedParentMethod" << endl; }
  void MChild() { cout << "ChildMethod" << endl; }
};

int main() {
  Child c;
  c.MParent();
  c.MChild();

  Parent& p = c;
  p.MParent();

  Child *cp = new Child;
  cp->MParent();
  cp->MChild();

  Parent *pp = cp;
  pp->MParent();

  delete cp;
  return 0;
}

OverridedParentMethod
ChildMethod
ParentMethod
OverridedParentMethod
ChildMethod
ParentMethod

実行結果

ちゃんと子クラスとして呼び出せば子クラス、親クラスにキャストすれば親クラスのメソッドが呼ばれていますね。何も問題はありません。

ところで、親クラスから子クラスに型変換しても親クラスのメソッドを呼び出す分には問題がなさそうにも思えます。同じようにstatic_castを使って呼び出してみましょう。

#include <iostream>
using namespace std;

class Parent {
public:
  void MParent() { cout << "ParentMethod" << endl; }
};

class Child : public Parent {
public:
  void MChild() { cout << "ChildMethod" << endl; }
};

int main() {
  Parent p;
  p.MParent();

  Child *cp = static_cast<Child*>(&p);
  cp->MParent();
  cp->MChild();
  return 0;
}

ParentMethod
ParentMethod
ChildMethod

実行結果

あれぇ、インスタンスを作っていない子クラスのメソッドを呼び出せてしまいました。この例では静的なメソッドを呼んだだけなので害は無いのですが、プロパティなど値を持っているものにアクセスすれば、その領域を確保していないので何が起こるかはおわかりですね?このように親クラスから子クラスへのキャストはダウンキャストと呼ばれ「安全ではない」と言われるのがこれが理由です。static_castではその変換で何らかのチェックが行われるわけではないので、どうしても必要になった場合であっても十分に慎重に使わなければなりません。確かに親クラスの情報しかアクセスしなければ問題ないのは確かなのですが。

親クラスで基本的な機能を定義して、子クラスで具体的な機能を追加して書いていくという方法はオブジェクト指向としては極めて普通のことですが、何らかの操作を行う処理を書く際に、子クラスごとに関数を用意するのはせっかくの抽象化が役に立ちません。親クラスの処理としてまとめて書いて実際の処理は子クラスの持つ、それぞれのメソッドが呼び出せてこそオブジェクトです。つまり親クラスから子クラスのメソッドを「安全に」呼び出したいことは、とても良くあります。そこでC++には、そんな方法も用意されているのですが、その話は次回にします。

ヘッダ画像は、以下のものを使わせていただきました。https://commons.wikimedia.org/wiki/File:ISO_C%2B%2B_Logo.svg
Jeremy Kratz - https://github.com/isocpp/logos , パブリック・ドメイン,
https://commons.wikimedia.org/w/index.php?curid=62851110による

#プログラミング #プログラミング言語 #プログラミング講座 #CPP #クラス #継承 #型変換 #キャスト #static_cast


この記事が気に入ったらサポートをしてみませんか?