Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

ポインタのキャストについてつらつらと

タグ: C++ Boost
#include <iostream>
#include <boost/implicit_cast.hpp>
#include <boost/cstdint.hpp>

// メモリ上のデータ配置例(32bit版)
// 4byte Base1::vptr
// 4byte Base1::_a = 1
class Base1
{
public:
    Base1() : _a(1) {}
    virtual ~Base1() {}

    int _a;
};

// メモリ上のデータ配置例(32bit版)
// 4byte Base2::vptr
// 4byte Base2::_b = 2
class Base2
{
public:
    Base2() : _b(2) {}
    virtual ~Base2() {}

    int _b;
};

// メモリ上のデータ配置例(32bit版)
// 4byte Base1::vptr
// 4byte Base1::_a = 1
// 4byte Derived1::_c = 3
class Derived1 : public Base1
{
public:
    Derived1() : _c(3) {}

    int _c;
};

// メモリ上のデータ配置例(32bit版)
// (0xbffff75c) 4byte Base1::vptr
// (0xbffff760) 4byte Base1::_a = 1
// (0xbffff764) 4byte Base2::vptr
// (0xbffff768) 4byte Base2::_b = 2
// (0xbffff76c) 4byte Derived2::_d = 4
class Derived2 : public Base1, public Base2
{
public:
    Derived2() : _d(4) {}

    int _d;
};

void output(const char* n, void* p)
{
    std::cout << n << " void*:" << p << std::endl;
}

void output(const char* n, Base1* p)
{
    std::cout << n << " Base1*:" << p;
    if (p)
    {
        std::cout << " _a:" << p->_a;
    }
    std::cout << std::endl;
}

void output(const char* n, Base2* p)
{
    std::cout << n << " Base2*:" << p;
    if (p)
    {
        std::cout << " _b:" << p->_b;
    }
    std::cout << std::endl;
}

void output(const char* n, Derived1* p)
{
    std::cout << n << " Derived1*:" << p;
    if (p)
    {
        std::cout << " _a:" << p->_a << " _c:" << p->_c;
    }
    std::cout << std::endl;
}

void output(const char* n, Derived2* p)
{
    std::cout << n << " Derived2*:" << p;
    if (p)
    {
        std::cout << " _a:" << p->_a << " _b:" << p->_b << " _d:" << p->_d;
    }
    std::cout << std::endl;
}

暗黙の型変換

出来ること:

  1. 任意の型のポインタ→void*
  2. 派生クラスのポインタ→基底クラスのポインタ(アップキャスト)
  3. 整数値0→任意の型のポインタ(NULLポインタ)

もっとも安全なキャスト。
元のポインタが正常なデータを指していれば、キャスト後のポインタも正常なデータを指す。

Derived2 obj;

// 1. 任意の型のポインタ→void*
void* p = &obj;
output("1.ok", p);

// 2. 派生クラスのポインタ→基底クラスのポインタ
Base1* base1 = &obj;
output("2.ok", base1);
Base2* base2 = &obj;
output("2.ok", base2); // OK: 継承関係を考慮して、&obj(=0xbffff75c)より8バイト後ろ(=0xbffff764)が指される
1.ok void*:0xbffff75c
2.ok Base1*:0xbffff75c _a:1
2.ok Base2*:0xbffff764 _b:2

boost::implicit_cast

出来ること:

  1. 暗黙の型変換と同じ

暗黙の型変換を明示的に行うテンプレート関数。
暗黙の型変換用のポインタ変数を用意するのが面倒な時に使う。
暗黙の型変換同様、キャスト前のポインタが正常ならキャスト後のポインタも正常。

Derived2 obj;

// 1. 暗黙の型変換と同じキャスト
output("1.ok", boost::implicit_cast<void*>(&obj));
output("1.ok", boost::implicit_cast<Base1*>(&obj));
output("1.ok", boost::implicit_cast<Base2*>(&obj));
1.ok void*:0xbffff75c
2.ok Base1*:0xbffff75c _a:1
2.ok Base2*:0xbffff764 _b:2

dynamic_cast

出来ること:

  1. 実行時型情報を持つクラスのポインタのアップ/ダウン/クロスキャスト

実行時にオブジェクトの型情報を評価し、正常にキャストできないようならNULLを返す。
キャスト前のポインタが正常ならキャスト後のポインタが評価不能な異常なデータを指すことはない。NULLチェックさえ怠らなければ安全なキャスト。
実行時に一切コストが発生しない他のキャストと違い、dynamic_castによるダウンキャストやクロスキャストは実行時に型情報を評価するため少しだけ遅くなる。

Derived2 obj;

// 1. 実行時型情報を持つクラスのポインタのアップ/ダウンキャスト
Base1* base1 = dynamic_cast<Base1*>(&obj);
Base2* base2 = dynamic_cast<Base2*>(&obj);
output("1.ok", base1);
output("1.ok", base2);
output("1.ok", dynamic_cast<Derived2*>(base2));// OK: 継承関係を考慮して、base2(=0xbffff75c)より8バイト後ろ(=0xbffff764)が指される
output("1.ok", dynamic_cast<Derived1*>(base2)); // OK: 継承関係がないことを考慮して「正常に」NULLを返す
1.ok Base1*:0xbffff75c _a:1
1.ok Base2*:0xbffff764 _b:2
1.ok Derived2*:0xbffff75c _a:1 _b:2 _d:4
1.ok Derived1*:0

static_cast

出来ること:

  1. 暗黙の型変換と同じキャスト
  2. void*→任意の型のポインタ
  3. 基底クラスのポインタ→派生クラスのポインタ(ダウンキャスト)

void*からのキャストもダウンキャストも、オブジェクト本来の型と異なる型のポインタへとキャストしてしまうと異常なデータを指すことになる。

reinterpret_castと比べると

  1. 継承関係のない型のポインタへキャストできない
  2. 「ポインタのポインタ」から「ポインタ」へといったキャストはできない

ので多少はうっかりを防止できるが、それでも異常なポインタを容易に作れる危険なキャストであることは意識しなければならない。

Derived2 obj;

// 1. 暗黙の型変換と同じキャスト
output("1.ok", static_cast<void*>(&obj));
output("1.ok", static_cast<Base1*>(&obj));
output("1.ok", static_cast<Base2*>(&obj));

void* p = &obj;

// 2. void*→任意の型のポインタ
output("2.ok", static_cast<Derived2*>(p));
output("2.ok", static_cast<Base1*>(p));
output("2.ng", static_cast<Base2*>(p)); // NG: _a のデータ領域を _b のデータ領域と解釈する

Base1* base1 = &obj;
Base2* base2 = &obj;

// 3. 基底クラスのポインタ→派生クラスのポインタ
output("3.ok", static_cast<Derived2*>(base1));
output("3.ok", static_cast<Derived2*>(base2)); // OK: 継承関係を考慮して、base2(=0xbffff764)より8バイト手前(=0xbffff75c)が指される
output("3.ng", static_cast<Derived1*>(base1)); // NG: Base2::vptr のデータ領域を Derived::1_c のデータ領域と解釈する
1.ok void*:0xbffff75c
1.ok Base1*:0xbffff75c _a:1
1.ok Base2*:0xbffff764 _b:2
2.ok Derived2*:0xbffff75c _a:1 _b:2 _d:4
2.ok Base1*:0xbffff75c _a:1
2.ng Base2*:0xbffff75c _b:1
3.ok Derived2*:0xbffff75c _a:1 _b:2 _d:4
3.ok Derived2*:0xbffff75c _a:1 _b:2 _d:4
3.ng Derived1*:0xbffff75c _a:1 _c:12504

reinterpret_cast

出来ること:

  1. 整数値⇔ポインタ
  2. 任意の型のポインタから、他の任意の型のポインタへの強制キャスト

Cスタイルキャストを含むC++の全てのキャストの中で唯一、あらゆるポインタを「オブジェクトを参照するもの」ではなくただの「整数値」として扱う。
継承関係を完全に無視するため、アップキャストやダウンキャストには使えない。C言語的には一番分かりやすいキャストとも言える。
「void*を介したstatic_cast」に近いが、「ポインタのポインタ」を「ポインタ」へキャストできるという点でより危険。
が、これはこれでreinterpret_castが必要なほど危険なキャストをしていることが一目で分かるため、意外とstatic_castより間違いを見つけやすかったりする、こともある。

Derived2 obj;

// 1. 整数値⇔ポインタ
boost::int64_t n = reinterpret_cast<boost::int64_t>(&obj);
output("1.ok", reinterpret_cast<Derived2*>(n));

Base1* base1 = &obj;
Base2* base2 = &obj;

// 2. 任意の型のポインタから、他の任意の型のポインタへの強制キャスト
output("2.ok", reinterpret_cast<Base1*>(&obj));
output("2.ng", reinterpret_cast<Base2*>(&obj)); // NG: _a のデータ領域を _b のデータ領域と解釈する
output("2.ok", reinterpret_cast<Derived2*>(base1));
output("2.ng", reinterpret_cast<Derived2*>(base2)); // NG: 継承関係が考慮されずに型だけ変わる
output("2.ng", reinterpret_cast<Derived1*>(&obj)); // NG: Base2::vptr のデータ領域を Derived::1_c のデータ領域と解釈する
1.ok Derived2*:0xbffff75c _a:1 _b:2 _d:4
2.ok Base1*:0xbffff75c _a:1
2.ng Base2*:0xbffff75c _b:1
2.ok Derived2*:0xbffff75c _a:1 _b:2 _d:4
2.ng Derived2*:0xbffff764 _a:2 _b:-1606460800 _d:-1606460800
2.ng Derived1*:0xbffff75c _a:1 _c:12504

Cスタイルキャスト

static_castできるならstatic_cast。そうでないならreinterpret_cast。みたいな感じの挙動。だったと思う。たしか。

スポンサーサイト

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://idlysphere.blog66.fc2.com/tb.php/244-70f1411c
この記事にトラックバックする(FC2ブログユーザー)

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。