ゲームフレームワーク的なものを作る。(1)〜Gameクラス〜

前回「windowsでゲームループを作る。(2)〜Windowクラス〜 - while( c++ );」のゲームループをさらに発展させて、ゲームフレームワーク的なものを適当に作っていきます。

Gameクラスを作る

ゲームを起動するためには、事前に様々なオブジェクトの初期化が必要で、また初期化は失敗する可能性があります。つまり、初期化成功後にゲームループを回すことになります。

int main()
{
    ウィンドウ生成;

    if( ゲームの初期化処理 )
    {
        //ゲームループ
        while( ... )
        {
            1フレームの処理;
        }
    }
    ゲームの解放処理;
    return 0;
}

ということで、以下のようなGameクラスを作ります。

  • コンストラク
    • 既存ウィンドウのweak_ptrを渡す
  • 初期化
  • 解放
  • 1フレームの処理
Gameクラス

ゲーム用ウィンドウはGameクラス外部で生成済みなので、GameクラスからWindowクラスの機能を利用するために、Windowクラスのポインタ(weak_ptr)をメンバとして持たせています。

#include <memory>

class Window;
typedef std::weak_ptr< Window > WindowWeakPtr;

/**
 *  Gameクラス
 */
class Game
{
    //  -------------------------------------------------------------------------------
    //  生成と破棄
    //  -------------------------------------------------------------------------------
public:
    /**
     *  コンストラクタ
     */
    Game( const WindowWeakPtr& window );

    //  -------------------------------------------------------------------------------
    //  基本機能
    //  -------------------------------------------------------------------------------
public:
    /**
     *  ゲームの初期化処理を行う
     */
    bool Initialize();
    /**
     *  ゲームの解放処理を行う
     */
    void Finalize();
    /**
     *  ゲーム1フレームの処理を行う
     */
    void Run();

private:
    WindowWeakPtr window;   ///<    既存ウィンドウを指すポインタ
};

以下、Gameクラスの実装。今回は特に何もしない。

#include "Game.h"

/**
 *  コンストラクタ
 *
 *  @param  window  既存ウィンドウのweak_ptr
 */
Game::Game( const WindowWeakPtr& window )
    : window( window )
{
}

/**
 *  ゲームの初期化処理を行う
 *
 *  @param  初期化成功時trueを、失敗時falseを返す
 */
bool Game::Initialize()
{
    return true;    //初期化成功
}

/**
 *  ゲームの解放処理を行う
 */
void Game::Release()
{
}

/**
 *  ゲーム1フレームの処理を行う
 */
void Game::Run()
{
}
使い方
/**
 *  エントリーポイント
 */
int main()
{
    //メモリリークの検出を有効化
    //  アウトプットウィンドウにメモリリーク時の情報が出力される
    ::_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

    //ゲーム用ウィンドウを生成
    WindowPtr window = Application::CreateGameWindow( "hoge", 720, 480 );
    if( !window )return 0;

    Game game( window );
    //ゲームを初期化して、ゲームループを開始する
    if( game.Initialize() )
    {
        //ゲームループ
        //  ウィンドウが閉じられるまで1フレームの処理を繰り返す
        while( !window->IsClosed() )
        {
            //1フレームの処理
            game.Run();

            //メッセージキューにある全てのwindowsイベントを処理する
            Application::DoEvents();
        }
    }
    game.Release();
    return 0;
}

サンプル

次回は、DirectXを使った描画機能を追加します。

6/6追記

某つぶやきでお叱りを頂いたので(名指しはされてないけど、タイミングと内容から自分のことかな)、ちょっと言い訳をすると、前回・今回からのエントリはC++初心者(C言語からC++へ移行中の方)を想定しています。最初は極力C++0x・boost的なコードは避けて、いずれC言語的な記述から、よりC++らしい記述へシフトしていこうと考えていました。が、次回から初期化・解放メソッドは廃止して、コンストラクタでの初期化・例外処理を使った形に書き換えていこうと思います。というか、初心者云々と偉そうなことを言えるほど、C++を理解してるわけじゃないですが…。

windowsでゲームループを作る。(2)〜Windowクラス〜

VC++2010を使い始めたので、出来るだけC++0xの機能を使いつつ、前回(http://d.hatena.ne.jp/setuna-kanata/20100512/1273685558)のプログラムを書き換えていきます。

Windowクラス

まずはHWNDのラッパークラスであるWindowクラスを作ります。今回必要になるメソッドは

  • コンストラク
  • ウィンドウが閉じられたかどうか調べるメソッドIsClosed
    • とりあえずIsWindow関数を使用

の2つです。

Window.h
#include <windows.h>

/**
 *  HWNDのラッパークラス
 */
class Window
{
public:
    /**
     *  コンストラクタ
     */
    Window( HWND hwnd );

public:
    /**
     *  ウィンドウが閉じられたか
     */
    bool IsClosed()const{ return ::IsWindow( hwnd ) == FALSE; }

private:
    HWND hwnd;
};
Window.cpp
#include "Window.h"

/**
 *  コンストラクタ。生成済みのウィンドウハンドルを渡す。
 */
Window::Window( HWND hwnd )
    : hwnd( hwnd )
{
}
Applicationクラス

C#風にwindowsイベントを処理するためにDoEvents関数を作ります。

  • windowsイベントを処理するためのメソッドDoEvents
  • ゲーム用ウィンドウを生成するメソッドCreateGameWindow
    • ウィンドウはstd::shared_ptr< Window >で返す
    • 前回同様、面倒なのでウィンドウプロシージャは用意せず、DefWindowProcでデフォルトの処理のみ行う。
Application.h
#include <string>
#include <memory>

class Window;
typedef std::shared_ptr< Window > WindowPtr;

class Application
{
public:
    /**
     *  メッセージキューにある全てのwindowsイベントを処理する。
     */
    static bool DoEvents();

    /**
     *  指定サイズのクライアント領域を持つ、ゲーム用の単純なウィンドウを作成する
     */
    static WindowPtr CreateGameWindow( const std::string& title, int client_width, int client_height );

};
Application.cpp
#include "Application.h"
#include "Window.h"
#include <iostream>

/**
 *  メッセージキューにある全てのwindowsイベントを処理する。
 *
 *  @return WM_QUITメッセージを受け取ったかどうか
 */
bool Application::DoEvents()
{
    MSG msg;
    for(;;)
    {
        if( ::PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            if( msg.message == WM_QUIT )return false;

            ::TranslateMessage( &msg );
            ::DispatchMessage( &msg );        //ウィンドウプロシージャの呼び出し
        }
        else
        {
            //1フレームの処理を行うためにゲームループへ処理を戻す
            return true;
        }
    }
}


/**
 *  指定サイズのクライアント領域を持つ、ゲーム用の単純なウィンドウを作成する
 *  @param  title   ウィンドウのタイトルを指定
 *  @param  width   クライアント領域の幅を指定
 *  @param  height  クライアント領域の高さを指定
 *  @return         生成したWindowクラスへのstd::shared_ptrを返す。失敗時はnullptrを返す。
 */
WindowPtr Application::CreateGameWindow( const std::string& title, int client_width, int client_height )
{
    const std::string class_name = "hoge";
    const DWORD style = WS_OVERLAPPEDWINDOW & ~( WS_MAXIMIZEBOX | WS_THICKFRAME );
    const DWORD exstyle = 0;
    const HINSTANCE instance = ::GetModuleHandle( nullptr );

    //ウィンドウクラスの登録
    WNDCLASSEX wc = { sizeof( WNDCLASSEX ) };
    wc.hInstance = instance;
    wc.lpszClassName = class_name.c_str();
    wc.lpfnWndProc = DefWindowProc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hIcon = static_cast< HICON >(
        ::LoadImage( nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED )
        );
    wc.hIconSm = wc.hIcon;
    wc.hCursor = static_cast< HCURSOR >(
        ::LoadImage( nullptr, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED )
        );
    wc.hbrBackground = static_cast< HBRUSH >(
        ::GetStockObject( WHITE_BRUSH )
        );
    if( !::RegisterClassEx( &wc ) )return nullptr;

    //クライアントサイズからウィンドウサイズを計算
    RECT rect = { 0, 0, client_width, client_height };
    ::AdjustWindowRectEx( &rect, style, FALSE, exstyle );

    //ウィンドウ生成
    HWND hwnd = ::CreateWindowEx(
        exstyle, class_name.c_str(), title.c_str(), style,
        CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top,
        nullptr, nullptr, instance, nullptr 
        );
    if( hwnd == nullptr )return nullptr;
    ::ShowWindow( hwnd, SW_SHOW );

    WindowPtr window( new Window( hwnd ) );

    std::cout << "ウィンドウを生成( " << title << "," << client_width << "," << client_height << " )" << std::endl;
    return window;
}
使い方
#include "Application.h"
#include "Window.h"

/**
 *  エントリーポイント
 */
int main()
{
    //メモリリークの検出を有効化
    //  アウトプットウィンドウにメモリリーク時の情報が出力される
    ::_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

    //ゲーム用ウィンドウを生成
    WindowPtr window = Application::CreateGameWindow( "hoge", 720, 480 );
    if( !window )return 0;
    
    //ゲームループ
    //  ウィンドウが閉じられるまで1フレームの処理を繰り返す
    while( !window->IsClosed() )
    {
        //1フレームの処理
        ;

        //メッセージキューにある全てのwindowsイベントを処理する。
        Application::DoEvents();
    }

    return 0;
}
サンプルのダウンロード

windowsでゲームループを作る。

C/C++windowsでゲームを作る時、ウィンドウクラスを登録して、ウィンドウを作って、メッセージループを回して、ウィンドウプロシージャを用意して…と、非常に面倒ですよね。

正直main関数内でそのようなつまらない記述を見たくないわけです。

もっと素直に

  • ウィンドウを作って
  • ウィンドウが閉じられるまでゲームループを回し、
  • 1フレームの処理

を記述したいのです。

int main()
{
    ゲーム用のウィンドウを生成;

    //ゲームループ
    while( ウィンドウが閉じられるまで )
    {
        1フレームの処理;
    }

    return 0;
}

ということで、以下適当にサンプルを。

main関数はできるだけシンプルに。
#include <windows.h>

int main()
{
    //ゲーム用のウィンドウを生成
    HWND hwnd = CreateGameWindow( "hoge", 720, 480 );
    if( hwnd == nullptr )return 0;

    //ゲームループ
    while( !IsClosed( hwnd ) )  //ウィンドウが閉じられるまで
    {
        //1フレームの処理
        ;

        //windowsイベントがあればウィンドウプロシージャを実行する。
        DoEvents();
    }

    return 0;
}
各関数の実装。

面倒だったので、ウィンドウプロシージャも無しの方向で。

/**
 *  指定サイズのクライアント領域を持つ、ゲーム用の単純なウィンドウを作成する
 *  @param  title   ウィンドウのタイトルを指定
 *  @param  width   クライアント領域の幅を指定
 *  @param  height  クライアント領域の高さを指定
 *  @return         生成したウィンドウのハンドル。失敗時はNULLを返す。
 */
HWND CreateGameWindow( const std::string& title, int client_width, int client_height )
{
    const std::string class_name = "hoge";
    const DWORD style = WS_OVERLAPPEDWINDOW & ~( WS_MAXIMIZEBOX | WS_THICKFRAME );
    const DWORD exstyle = 0;
    const HINSTANCE instance = GetModuleHandle( nullptr );

    //ウィンドウクラスの登録
    WNDCLASSEX wc = { sizeof( WNDCLASSEX ) };
    wc.hInstance = instance;
    wc.lpszClassName = class_name.c_str();
    wc.lpfnWndProc = DefWindowProc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hIcon = static_cast< HICON >(
        LoadImage( nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED )
        );
    wc.hIconSm = wc.hIcon;
    wc.hCursor = static_cast< HCURSOR >(
        LoadImage( nullptr, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED )
        );
    wc.hbrBackground = static_cast< HBRUSH >(
        GetStockObject( WHITE_BRUSH )
        );
    if( !RegisterClassEx( &wc ) )return nullptr;

    //クライアントサイズからウィンドウサイズを計算
    RECT rect = { 0, 0, client_width, client_height };
    AdjustWindowRectEx( &rect, style, FALSE, exstyle );

    //ウィンドウ生成
    HWND hwnd = CreateWindowEx(
        exstyle, class_name.c_str(), title.c_str(), style,
        CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top,
        nullptr, nullptr, instance, nullptr 
        );
    if( hwnd == nullptr )return nullptr;
    ShowWindow( hwnd, SW_SHOW );

    return hwnd;
}

/**
 *  windowsイベントがあればプロシージャを実行する。
 *
 *  @return WM_QUITメッセージを受け取ったかどうか
 */
bool DoEvents()
{
    MSG msg;
    for(;;)
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            //ウィンドウが閉じられた
            if( msg.message == WM_QUIT )return false;

            TranslateMessage( &msg );
            DispatchMessage( &msg );        //ウィンドウプロシージャの呼び出し
        }
        else
        {
            //ゲームループで1フレームの処理を行う
            return true;
        }
    }
}

/**
 *  ウィンドウが閉じられたか
 *
 *  @param  hwnd    ウィンドウハンドル
 */
bool IsClosed( HWND hwnd )
{
    return ::IsWindow( hwnd ) == FALSE;
}
  • ウィンドウが閉じられたかどうか調べる際に、IsWindowだけ問題ないのか
実行結果


サンプルのダウンロード

variantの実装(4) -static_visitor-

前回(variantの実装(3) -placement new- - while( c++ );)のつづき。

variantに任意のオブジェクトを格納することが出来たので、今回はオブジェクトを取り出してみます。


variantに格納されているのオブジェクト(storage_)の型は、メンバwhich_で型に対応する番号として管理されています。

template< typename T1, typename T2, ..., typename TN >
class variant
{
    storage_type storage_;
    int which_;   //T1 -> 1
                  //T2 -> 2
                  //  ...
                  //TN -> N
};

ということは、前回のデストラクタのようにwhich_の値でキャストすればよさそうですね。

template< typename T1, typename T2, ..., typename TN >
class variant
{
    int which_;   //T1 -> 1
                  //T2 -> 2
                  //  ...
                  //TN -> N

    T& get()const
    {
        switch( which_ )
        {
        case 1:
            return *static_cast< T1* >( storage_.address() );
            break;
        case 2:
            return *static_cast< T2* >( storage_.address() );
            break;

        ...

        case N:
            return *static_cast< TN* >( storage_.address() );
            break;
        }

        return ???;
    }
};

しかし、上記の例ではコンパイルエラーになってしまいます。戻り値の型とstorage_の型が一致しないのでreturn出来ません。

static_visitor

boostではstatic_visitorのオーバーロードによって上記の問題を解決しています。

template< typename T = void >
struct static_visitor
{
    typedef T result_type;

protected:
    static_visitor(){}
    ~static_visitor(){}
};


template< typename T >
struct visitor
    : static_visitor< T* >
{
    //variantのwhich_とTが一致する場合、storage_のアドレスを返す
    result_type operator()( T& storage )const
    {
        return &storage;
    }

    //variantのwhich_とTが一致しない場合、nullを返す
    template< typename U >
    result_type operator()( U& )const
    {
        return 0;
    }
};
template< typename T1, typename T2, ..., typename TN >
class variant
{
    //storage_に格納されている値を取得
    template< typename T >
    T& get()const
    {
        visitor< T > v;
        T* p = accept( v );
        //storage_の型とTが一致しない
        if( p == 0 )throw bad_cast();

        return *p;
    }

    //visitorの受け入れ
    template< typename Visitor >
    typename Visitor::result_type accept( Visitor& v )const
    {
        switch( which_ )
        {
        case 1:
            return internal_visit< T1 >( v );
            break;
        case 2:
            return internal_visit< T2 >( v );
            break;

        ...

        case N:
            return internal_visit< TN >( v );
            break;
        }
        return internal_visit< void_ >( v );
    }

    template< typename T, typename Visitor >
    typename Visitor::result_type internal_visit( Visitor& v )const
    {
        return v( *static_cast< T* >( storage_.address() ) );
    }
};

使ってみる

#include "variant.h"

int main()
{

    variant< int, std::string, double > v( std::string( "hoge" ) );

    std::cout << v.get< std::string >() << std::endl;    //hoge
    std::cout << v.get< int >() << std::endl;            //例外がthrowされる

    return 0;
}

destroy_visitor

variantのデストラクタ内の処理もstatic_visitorで書き換えてみましょう。
storageに格納されているのオブジェクトのデストラクタを呼び出すdestroy_visitorを作ります。

struct destroy_visitor
    : static_visitor<>
{
    template< typename T >
    void operator()( T& operand )
    {
        operand;        //warningの回避

        operand.~T();
    }
};
template< typename T1, typename T2, ..., typename TN >
class variant
{
    ~variant()
    {
        destroy();
    }

    //storage_に格納されているオブジェクトのデストラクタを呼び出す
    void destroy()
    {
        destroy_visitor visitor;
        accept( visitor );
    }
};

ここまでのサンプル

次回は、代入演算子、コピーコンストラクタなどを実装予定です。
ただ、例外安全を考えると面倒だなあ。。。
melponさんが解説しています。

variantの実装(3) -placement new-

前回(variantの実装(2) -aligned_storageを作る- - while( c++ );)のつづき。

参考

アライメントの問題はおそらく解決できたので、placement newを使ってスタック上の未初期化領域に任意のオブジェクトを生成してみます。

class Hoge
{
public:
    Hoge( char a = 0, int b = 0, short c = 0 )
        : a( a ), b( b ), c( c )
    {}
private:
    char a;
    int b;
    short c;
};

int main()
{
    typedef aligned_storage
        < sizeof( Hoge )                //12
        , alignment_of< Hoge >::value   //4
        > storage_t;

    storage_t storage;
    Hoge* p = new( storage.address() ) Hoge( 1, 2, 3 );

    p->~Hoge();

    return 0;
}

デバッガで確認するとこんな感じです。

たぶん問題無いですよね?

variant

これでvariantに任意の型のオブジェクトを格納できるようになりました。
早速、本格的にvariantの実装をしていきましょう。ワクワク。
(このエントリを書いてる時点で、とりあえずboost::variantっぽいものは出来てるんですけどね)

実装
template< typename T1, typename T2, ..., typename TN >
class variant
{
    typedef variant this_type;
    typedef typelist< typename T1, typename T2, ..., typename TN > list_type;
    typedef typename make_storage< list_type >::type storage_type;

    storage_type storage_;
    int which_;            //storage_に格納されているオブジェクトの型を表す番号1〜N

public:
    template< typename T >
    variant( const T& value )
    {
        //型リストからTに一致する型を見つけてストレージに格納する
        typedef find< T, list_type > found;

        new ( storage_.address() ) typename found::type( value );
        which_ = found::value;
    }
};

make_storage

型リストからストレージ型を作成するメタ関数。

実装
//型リストからストレージ型を作成
template< typename TypeList >
struct make_storage
{
    typedef aligned_storage
        < TypeList::max_size
        , TypeList::max_alignment
        > type;
};

find

型リストからTに一致する型を探し、かつその番号(1〜)を取得する。見つからない場合、void_を返すメタ関数。

実装
//型リストからTを検索、かつその番号を取得。見つからない場合はvoid_を返すメタ関数。
template< typename T, typename TypeList, int N = 1, bool stop = ( N > TypeList::length ) >
struct find
{
private:
    //型リスト内のTNのtype、valueを取得
    typedef get_n< TypeList, N > get;

    //Tと一致する型を再帰的に検索
    typedef typename selector
        < is_same< typename get::type, T >::value
        , get
        , find< T, TypeList, N + 1 >
        >::type found;
public:
    typedef typename found::type type;
    enum{ value = found::value };
};

template< typename T, typename TypeList, int N >
struct find< T, TypeList, N, true >
{
    typedef void_ type;
    enum{ value = 0 };
};

get

型リストのN番目の型と番号を取得するメタ関数。

//型リストのN番目の型と番号を取得
template< typename TypeList, int N >
struct get_n;

//
//template< typename TypeList >
//struct get_n< TypeList, 1 >{ enum{ value = 1 }; typedef typename TypeList::type1 type; };
//template< typename TypeList >
//struct get_n< TypeList, 2 >{ enum{ value = 2 }; typedef typename TypeList::type2 type; };
//...
//template< typename TypeList >
//struct get_n< TypeList, n >{ enum{ value = n }; typedef typename TypeList::typen type; };
//
#define pp_get_n( n, p ) \
    template< typename TypeList > \
    struct get_n< TypeList, n > \
    { \
        enum { value = n }; \
        typedef typename TypeList::type##n type; \
    };
pp_repeat( max_typelist_params, pp_get_n, pp_get_n, ignore );

variant

variantのデストラクタでは、型の番号に合わせてストレージをキャストし、デストラクタを呼び出します。

template< typename T1, typename T2, ..., typename TN >
class variant
{
    ...

    ~variant()
    {
        //型に対応するデストラクタを呼び出す
        switch( which_ )
        {
        case 1:
            static_cast< T1* >( storage_.address() )->~T1();
            break;
        case 2:
            static_cast< T2* >( storage_.address() )->~T2();
            break;

        ...

        case N:
            static_cast< TN* >( storage_.address() )->~TN();
            break;
        }
    }
};

型リストにintやfloatなどのprimitiveな型が含まれる場合、

            static_cast< int* >( storage_.address() )->~int();

となり、コンパイルエラーになりそうですが、コンパイラによって無視?されるようです。

また、switch文の各caseはテンプレート引数の個数Nに依存するので、プリプロセッサを使って書き直します。

    //case n:
    //    static_cast< TN* >( storage_.address() )->~TN();
    //    break;
#define variant_case( n, p ) \
    case n: \
        static_cast< pp_cat( T, n )* >( storage_.address() )->~pp_cat( T, n )(); \
        break;

        switch( which_ )
        {
            pp_repeat( max_typelist_params, variant_case, variant_case, ignore );
        }
#undef variant_case

使ってみる

#include "variant.h"

int main()
{
    //variant< int, std::string, double > v( 1 );
    variant< int, std::string, double > v( std::string( "hoge" ) );
    //variant< int, std::string, double > v( 3.14 );
    //variant< int, std::string, double > v( 3.14f );  //コンパイルエラー

    return 0;
}


ここまでのサンプル


次回は、ストレージから値を取得するためのget、operator=、visitorなどを実装予定です。

variantの実装(2) -aligned_storageを作る-

前回(variantの実装(1) -みんな大好きテンプレートメタプログラミング!- - while( c++ );)のコメントで、アライメントの問題に関してご指摘を頂いたので、variantの実装の前にaligned_storageを実装してみたいと思います。
ただしVC++2008だけを想定した手抜き実装です。

今回の目標

template< std::size_t size_, std::size_t alignment_ >
struct aligned_storage
{
    union data_t
    {
        char buffer[ size_ ];
        typename type_with_alignment< alignment_ >::type align;
    }data;
};
    //サイズ==8、アライメント==4のストレージ
    typedef aligned_storage< 8, 4 > storage_t;
    std::cout
        << sizeof( storage_t ) << std::endl                     //8
        << alignment_of< storage_t >::value << std::endl        //4
        ;
    //アライメントが2の乗数以外の時はstatic_assertでコンパイルエラーにする
    typedef aligned_storage< 8, 3 > storage_t;

以上のような記述ができるように、

  • alignment_of
  • aligned_storage
  • type_with_alignment
  • integral_constant
  • static_assert

を作ります。

alignment_of

型Tのアライメントを求めるメタ関数。

使い方
size_t a = alignment_of<T>::value;
実装

std::tr1の実装を参考にしました。というかパクリですね。

template< typename T >
struct get_align_
{
    T t1;
    char c;
    T t2;
};
#define get_align( T ) ( sizeof( get_align_< T > ) - 2 * sizeof( T ) )

template< typename T >
struct alignment_of
    : integral_constant< size_t, get_align( T ) >
{};

//referenceのアライメントはpointerとして扱う
template< typename T >
struct alignment_of< T& >
    : integral_constant< size_t, get_align( T* ) >
{};

boostではalignment_of_hackとして以下のように実装されています。ちょっとだけ改変。

template <typename T>
struct alignment_of_hack
{
    char c;
    T t;
    alignment_of_hack();
};

template <typename T>
struct alignment_of
    : integral_constant< size_t, sizeof(::boost::detail::alignment_of_hack<T>) - sizeof(T) >
{};

type_with_alignment

指定したアライメントの型を返すメタ関数。
この実装のでかなり悩んだのですが、結局以下のようなVC++限定の手抜き実装にしました^^
アライメントは2の乗数のみ指定できます。

実装
template< std::size_t Align >
struct type_with_alignment_impl
{
    //とりあえずAlignが2の乗数以外の時はコンパイルエラーにする
    static_assert( ( Align > 0 ) && ( ( Align & ( Align - 1 ) ) == 0 ) );
};

template< std::size_t Align >
struct type_with_alignment
    : type_with_alignment_impl< Align >{};

struct                              a1{      __int8 m; };
struct                              a2{     __int16 m; };
struct                              a4{     __int32 m; };
struct                              a8{     __int64 m; };
struct __declspec( align(  16 ) )  a16{ char m[  16 ]; };
struct __declspec( align(  32 ) )  a32{ char m[  32 ]; };
struct __declspec( align(  64 ) )  a64{ char m[  64 ]; };
struct __declspec( align( 128 ) ) a128{ char m[ 128 ]; };

template<> struct type_with_alignment<   1 >{ typedef   a1 type; };
template<> struct type_with_alignment<   2 >{ typedef   a2 type; };
template<> struct type_with_alignment<   4 >{ typedef   a4 type; };
template<> struct type_with_alignment<   8 >{ typedef   a8 type; };
template<> struct type_with_alignment<  16 >{ typedef  a16 type; };
template<> struct type_with_alignment<  32 >{ typedef  a32 type; };
template<> struct type_with_alignment<  64 >{ typedef  a64 type; };
template<> struct type_with_alignment< 128 >{ typedef a128 type; };

static_assert

コンパイルタイムassert。
条件がfalseであれば、static_assert_failureが未定義となり、以下のようなコンパイルエラーになる。

error C2027: 認識できない型 'static_assert_failure<b>' が使われています。
実装
template< bool b >struct static_assert_failure;
template<>struct static_assert_failure< true >{ enum{ value = 1 }; };

template< int x >struct static_assert_test{};

//compile time assert
#define static_assert( b ) \
    typedef static_assert_test \
        < sizeof( static_assert_failure< ( bool )(b) > ) > \
        static_assert_typedef;

aligned_storage

char buffer[ size_ ]とtype_with_alignmentのunionを用意すれば、bufferのアライメントを指定できる。

実装
//
//size_       bufferのサイズを指定
//alignment_  アライメントを指定
//
template< std::size_t size_, std::size_t alignment_ >
struct aligned_storage
{
    union data_t
    {
        char buffer[ size_ ];
        
        //alignment_に対応する型
        //  1   __int8
        //  2   __int16
        //  4   __int32
        //  8   __int64
        typename type_with_alignment< alignment_ >::type align;
    }data;

    void* address()const{ return const_cast< aligned_storage* >( this ); }
};

使ってみる

    typedef aligned_storage< 8, 4 > storage_t;
    std::cout
        << sizeof( storage_t ) << std::endl                     //8
        << alignment_of< storage_t >::value << std::endl        //4
        ;


次回はaligned_storageを使ってvariantを実装します。

variantの実装(1) -みんな大好きテンプレートメタプログラミング!-

multi-type, single value.

1つの変数で複数の型を扱いたい場合、C言語ではunionを使用しますが、
C++ unionでは、std::stringを扱うことが出来ません。

union
{
    int a;
    double b;
    std::string c;   //illegal: std::string is not a POD type!
};

C++でunion的なものを使いたい場合、Boost.Variantが便利です。

boost::variant< int, double, std::string > v;
v = 1;
v = 2.0;
v = "hoge";

型リストの中で最大サイズの型(上記の例ではstd::stringの32バイト)と同じサイズのstorageが作られ、placement newによってオブジェクトが構築されます。

今回は以上のような型リストに対応する値を格納できるvariantクラスを作ってみます。
ただし、boostの実装は今の私のレベルではハードルが高すぎるので、TTL(Tiny Template Library)をベースに実装してみます。

まずはstorageを作るための準備として、いくつかのメタ関数とpreprocessorマクロを用意します。

  • typelist_traits
    • lengtht…型リストの要素数
    • largest_type…型リストの最大サイズの型
  • pp_cat…トークンの連結
  • pp_inc…preprocessorによる定数のインクリメント
  • pp_dec…preprocessorによる定数のデクリメント
  • pp_repeat…指定マクロの繰り返し
    • pp_typename_T_def…typename T1 = def, typename T2 = def, ..., typenmae Tn = def
    • など

型リストの要素数

空の型を表すvoid_以外の型の個数(型リスト内の最も右にあるvoid_以外の型までの要素数)を求めるメタ関数。
型リストの要素を再帰的に左シフトさせて、全ての要素をvoid_にします。
全ての要素がvoid_になると、特殊化によって再帰が停止します。

struct void_{};  //empty_type

template< typename T1 = void_, typename T2 = void_, typename T3 = void_ typename T4 = void_ >
struct typelist_traits
{
    //型リストを再帰的に左シフトすることで、いずれ全てvoid_になる
    //     T2,    T3,    T4, void_
    //     T3,    T4, void_, void_
    //     T4, void_, void_, void_
    //  void_, void_, void_, void_
    typedef typelist_traits< T2, T3, T4, void_ > list;

    //型リストの要素数
    //  typelist_traits< int >::length == 1
    //  typelist_traits< int, double, std::string >::length == 3
    enum{ length = 1 + list::length };
    //  length = 1 + typelist_traits< T2, T3, T4, void_ >::length
    //  length = 1 + ( 1 + typelist_traits< T3, T4, void_, void_ >::length )
    //  length = 1 + ( 1 + ( 1 + typelist_traits< T4, void_, void_, void_ >::length ) )
    //  length = 1 + ( 1 + ( 1 + ( 1 + typelist_traits< void_, void_, void_, void_ >::length ) ) )
    //  length = 1 + ( 1 + ( 1 + ( 1 + 0 ) ) )
};

//全ての要素がvoid_の時の特殊化
template<>
struct typelist_traits< void_, void_, void_, void_ >
{
    typedef void_ list;

    enum{ length = 0 };
};

上記の例では4つのテンプレート引数しか渡せないですが、preprocessorを使って任意の個数を渡すことが可能です。
事前にBoost.Preprocessorで遊んでおくと読みやすくなると思います。
また、C++0xのVariadic Templatesを使うともっと簡単に記述できるかもしれません。

struct void_{}; //empty_type

#define max_typelist_params 10

template< pp_typename_T_def( max_typelist_params, void_ ) >
//template< typename T1 = void_, typename T2 = void_, ..., typename T10 = void_ >
struct typelist_traits
{
    //型リストを再帰的に左にシフトすることで、いずれ全てvoid_になる
    typedef typelist_traits< pp_args_lshift_T( pp_dec( max_typelist_params ) ) > list;
    //typedef typelist_traits< T2, T3, ..., T9, T10, void_ ) > list;
    //typedef typelist_traits< T3, T4, ..., T10, void_, void_ ) > list;
    //...
    //typedef typelist_traits< T10, void_, ..., void_, void_, void_ ) > list;
    //typedef typelist_traits< void_, void_, ..., void_, void_, void_ ) > list;

    //型リストの要素数を求める
    //  typelist_traits< int >::length == 1
    //  typelist_traits< int, double, std::string >::length == 3
    enum{ length = 1 + list::length };
};

//全ての要素がvoid_の時の特殊化
template<>
struct typelist_traits< pp_repeat_t( max_typelist_params, void_ ) >
//struct typelist_traits< void_, void_, ..., void_ >
{
    typedef void_ list;

    enum{ length = 0 };
};
pp_typename_T_def
//繰り返し「typename T1 = p, typename T2 = p, ..., typename Tn = p」
#define pp_typename_T_def_n( n, p ) typename T##n = p, 
#define pp_typename_T_def_n_end( n, p ) typename T##n = p

#define pp_typename_T_def( n, p ) pp_repeat( n, pp_typename_T_def_n, pp_typename_T_def_n_end, p )
pp_args_lshift_T
//「T1, T2, ..., Tn」
//    ↓左シフト
//「T2, T3, ..., Tn+1」
#define pp_args_lshift_t_n( n, t ) pp_cat( t, pp_inc( n ) ),
#define pp_args_lshift_t_n_end( n, t ) pp_cat( t, pp_inc( n ) )

#define pp_args_lshift_T( n ) pp_repeat( n, pp_args_lshift_t_n, pp_args_lshift_t_n_end, T )
pp_repeat_t
//繰り返し「t, t, ..., t」
#define pp_repeat_t_n( n, t ) t,
#define pp_repeat_t_n_end( n, t ) t

#define pp_repeat_t( n, t ) pp_repeat( n, pp_repeat_t_n, pp_repeat_t_n_end, t )
pp_repeat
#define pp_repeat_0(m,p)
#define pp_repeat_1(m,p) pp_repeat_0(m,p) m(1,p)
#define pp_repeat_2(m,p) pp_repeat_1(m,p) m(2,p)
...
#define pp_repeat_255(m,p) pp_repeat_254(m,p) m(255,p)
#define pp_repeat_256(m,p) pp_repeat_255(m,p) m(256,p)

#define pp_last_repeat_0(m,p)
#define pp_last_repeat_1(m,p) m(1,p)
#define pp_last_repeat_2(m,p) m(2,p)
...
#define pp_last_repeat_255(m,p) m(255,p)
#define pp_last_repeat_256(m,p) m(256,p)

//m(1, p)〜m(n-1, p)の繰り返しとl(n, p)
#define pp_repeat(n, m, l, p) pp_cat(pp_repeat_, pp_dec(n))(m,p) pp_cat(pp_last_repeat_,n)(l,p)
pp_inc
//increment
//pp_inc(10) -> 11
#define pp_inc( n ) pp_inc_n( n )
#define pp_inc_n( n ) pp_cat( pp_inc_, n )

#define pp_inc_0 1
#define pp_inc_1 2
...
#define pp_inc_254 255
#define pp_inc_255 256
pp_dec
//decrement
//pp_dec(10) -> 9
#define pp_dec( n ) pp_dec_n( n )
#define pp_dec_n( n ) pp_cat( pp_dec_, n )

#define pp_dec_1 0
#define pp_dec_2 1
...
#define pp_dec_255 254
#define pp_dec_256 255
pp_cat
//トークン連結
#define pp_cat( a, b ) pp_cat_( a, b )
#define pp_cat_( a, b ) a##b

最大サイズの型

型リスト内の最大サイズの型を調べるためのメタ関数。

  • selector
    • type…bool値による型の選択
  • storage
  • make_storage
    • type…型リスト内の最大サイズの型と同じサイズのstorageを作る
  • variant
bool値で型の選択
//型選択
//typedef selector< true, int, float >::type type1;    //int
//typedef selector< false, int, float >::type type2;   //float
//type1 a; //int a;
//type2 b; //float b;
template< bool condition, typename T1, typename T2 >
struct selector
{
    typedef T1 type;
};

//bool condition = false時の特殊化
template< typename T1, typename T2 >
struct selector< false, T1, T2 >
{
    typedef T2 type;
};
最大サイズの型
template< pp_typename_T_def( max_typelist_params, void_ ) >
struct typelist_traits
{
    //型リストを再帰的に左にシフトすることで、いずれ全てvoid_になる
    typedef typelist_traits< pp_args_lshift_T( pp_dec( max_typelist_params ) ) > list;

    //型リストの要素数
    enum{ length = 1 + list::length };

    //最大サイズの型
    typedef typename selector
        < sizeof( T1 ) >= sizeof( list::largest_type )
        , T1
        , typename list::largest_type
        >::type largest_type;
};

//全ての要素がvoid_の時の特殊化
template<>
struct typelist_traits< pp_repeat_t( max_typelist_params, void_ ) >
{
    typedef void_ list;

    enum{ length = 0 };

    typedef void_ largest_type;
};

最大サイズの型に合わせたstorage

//variant用ストレージ
template< int N >
struct storage
{
    char buffer[ N ];
};

//型リストからストレージを作成
template< typename TypeList >
struct make_storage
{
    typedef storage< sizeof( typename TypeList::largest_type ) > type;
};

variant

//ストレージ持っているだけのvariant
template< pp_typename_T_def( max_typelist_params, void_ ) >
struct variant
{
    typedef variant this_type;
    typedef typelist< pp_args_T( max_typelist_params ) > list;
    typedef typename make_storage< list >::type storage_t;

    storage_t storage_;
};

使ってみる

型リスト内の最大サイズの型と同じサイズのストレージが用意されたことを確認します。

int main()
{
    variant< std::string, int, double > v;
    std::cout << sizeof( v ) << std::endl;

    return 0;
}
実行結果
32

サンプル

次回はplacement newを使って実際に値を格納します。