shonen.hateblo.jp

やったこと,しらべたことを書く.

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_caststatic_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