C++での生ポインタを使わないクラスのキャスト
C++でまともにクラス継承を書いたことが無かった事に今更気づくなど.
目的
次のようなコードを書きたいとする.
ポインタを使うと次のように書けるが,可能な限りスマートポインタとして扱いたい.
#include "bits/stdc++.h" using namespace std; struct P { int type; P(int t) :type(t) { printf("new:P\n"); } ~P() { printf("del:P\n"); } virtual void print() = 0; }; struct Ca : public P { int data; Ca(int d) :P(1) { data = d; printf("new:Ca(%d)\n", d); } ~Ca() { printf("del:Ca(%d)\n", data); } void print() { printf("C1: %d\n", data); } }; struct Cb : public P { array<int, 8> data; Cb(int d) :P(2) { for (int i = 0; i<8; ++i) data[i] = d*i + d; printf("new:Cb(%d)\n", d); } ~Cb() { printf("del:Cb(%d)\n", data[0]); } void print() { printf("C2:"); for (int i = 0; i<8; ++i)printf(" %d", data[i]); printf("\n"); } int sum() { return accumulate(data.begin(), data.end(), 0); } }; int main() { vector<P*> lis = {new Ca(1), new Ca(2), new Cb(3), new Cb(4)}; for (P* p : lis) { p->print(); if (p->type == 2) { Cb* cb = (Cb*)p; printf("sum: %d\n", cb->sum()); } } for (P* p : lis) { if (p->type == 1){ delete (Ca*)p; }else if (p->type == 2) { delete (Cb*)p; }else{ delete p; } } }
output
new:P new:Ca(1) new:P new:Ca(2) new:P new:Cb(3) new:P new:Cb(4) C1: 1 C1: 2 C2: 3 6 9 12 15 18 21 24 sum: 108 C2: 4 8 12 16 20 24 28 32 sum: 144 del:Ca(1) del:P del:Ca(2) del:P del:Cb(3) del:P del:Cb(4) del:P
やってみる
要素をリストに保持.
まずは,shared_ptr<P>
型のvector
を用意して,要素を入れていく.
vector<shared_ptr<P>> lis; lis.emplace_back(new Ca(1)); lis.emplace_back(new Ca(2)); lis.emplace_back(new Cb(3)); lis.emplace_back(new Cb(4));
unique_ptr
ではなくshared_ptr
を使っているのは,後の型変換で別のスマートポインタと共有するため.
ちなみに,unique_ptr
だとP
のデストラクタしか呼ばれない.
リストの要素ごとに(共通の親クラスの)メンバ関数を呼びたい.
殆ど生ポインタと一緒
for (shared_ptr<P>& p : lis) {
p->print();
}
リストの要素ごとに(子クラスの)メンバ関数を呼びたい.
書いてしまえば殆ど生ポインタと一緒だということが分かる.
for (shared_ptr<P>& p : lis) { p->print(); if (p->type == 2) { shared_ptr<Cb> cb(dynamic_pointer_cast<Cb>(p)); printf("sum: %d\n", cb->sum()); } }
個人的に分かりにくかったのはdynamic_pointer_cast
.static_pointer_cast
と何が違うの.
dynamicとstaticの違いについて調べたが,visual studio 2015
のリファレンスが一番参考になったので紹介.
https://msdn.microsoft.com/ja-jp/library/c36yw7x9.aspx
- 端的に言うと,
dynamic
の方が安全. Ca
からP
への変換を考えてみる.- 構造体が持つメンバ変数のサイズは減少しているので,範囲外参照は起こり得ないと考えて良い.
static_pointer_cast
を用いて良い.
P
からCa
への変換を考えてみる.- 構造体が持つメンバ変数のサイズは増加しているので,実体が
Ca
に属さない場合,範囲外参照が起こる場合がある. dynamic_pointer_cast
でチェックを行うと安全である.
- 構造体が持つメンバ変数のサイズは増加しているので,実体が
結論
// include・構造体は省略 int main() { vector<shared_ptr<P>> lis; lis.emplace_back(new Ca(1)); lis.emplace_back(new Ca(2)); lis.emplace_back(new Cb(3)); lis.emplace_back(new Cb(4)); for (shared_ptr<P>& p : lis) { p->print(); if (p->type == 2) { shared_ptr<Cb> cb(static_pointer_cast<Cb>(p)); printf("sum: %d\n", cb->sum()); } } return 0; }
output
new:P new:Ca(1) new:P new:Ca(2) new:P new:Cb(3) new:P new:Cb(4) C1: 1 C1: 2 C2: 3 6 9 12 15 18 21 24 sum: 108 C2: 4 8 12 16 20 24 28 32 sum: 144 del:Ca(1) del:P del:Ca(2) del:P del:Cb(3) del:P del:Cb(4) del:P