hoge diary - October 2006

October 30, 2006

[C++] delegate in C++

C# ではおなじみの delegate (デリゲート)ですが,C++/CLI でないただの C++ には当然そんなものはありません.今私が作っている C++ プログラムの機能を部分的にプラグイン化するにあたって,C# にあるあの便利な delegate の C++ 版がないかと思って探していると,いくつかの実装例を見つけました.

どちらの手法にも言えることは関数の引数(シグネチャ)をプログラム中にハードコーディングする必要がある,という点です.関数ポインタを使ってデリゲートを実装する以上,型をあらかじめ書く必要があるので仕方ないでしょう.

そんな中,Perl スクリプトを使用して,任意のシグネチャに対するデリゲートクラスを自動生成するアプローチがありました.

このスクリプトは,C# の delegate の定義によく似たファイル(以下)を入力として,

delegate void MyCallback( int a, int b, int c );

このファイルから,次のような C++ のヘッダファイルを生成する,というものです.

class MyCallback
{
public:
        virtual void Call( int a, int b, int c ) = 0;
};

template< class T >
class MyCallback_Delegate : public MyCallback
{
private:
        T* _that;
        void (T::*_func)( int a, int b, int c );

public:
        MyCallback_Delegate( T* that, void (T::*func)( int a, int b, int c ) )
        {
                _that = that;
                _func = func;
        }

        virtual void Call( int a, int b, int c ) {
                (_that->*_func)( a, b, c );
        }
};

template< class T >
inline MyCallback_Delegate<T>* new_MyCallback( T* that, void (T::*func)( int a, int b, int c ) )
{
        return new MyCallback_Delegate<T>( that, func );
}

呼び出す際には,次のようにします.

  1. まず最初に,デリゲート型のインスタンスを生成し,
    MyCallback* cb = new_MyCallback( this, &Receiver::DoStuff );
  2. 生成したインスタンスを使用して,呼び出しを行います.
    static void CallMeBack( MyCallback* cb )
    {
        cb->Call( 1, 2, 3 );
    }

関数ポインタの型が

void (T::*_func)( int a, int b, int c );

となっていますので,クラスの非 static なメンバ関数を呼び出せます.

この方法は,C++ における delegate の実装のうち,自動化できるところは徹底的に自動化したバージョンといえるでしょう.試しに使ってみようと思います.

なお,同じ文書中に,Perl スクリプトを用いない方法についても書いてありました.ただ,こちらの手法は渡す引数の数(型は問わない)に応じたテンプレートクラスを自分で書く必要があります.

October 12, 2006

[C++] 符号付き整数と符号なし整数の引数オーバーロード (2)

以前書いた符号付き整数と符号なし整数の引数オーバーロードの続編です.

あの時は autoconf を使って size_t がどの型なのかを判別すればいい,と書きましたが,後になって考えてみると,そんなことをする必要はないという考えに至りました.

int, unsigned int, long, unsigned long のそれぞれでオーバーロードしておけばいいだけの話でした.

#include <cstdio>

class A
{
private:
    int b;
public:
    A(int i) { printf("%s\n", __PRETTY_FUNCTION__); }
    A(unsigned int i) { printf("%s\n", __PRETTY_FUNCTION__); }
    A(long i) { printf("%s\n", __PRETTY_FUNCTION__); }
    A(unsigned long i) { printf("%s\n", __PRETTY_FUNCTION__); }
};

こうしておけば,size_t が unsigned int, unsigned long いずれの型であっても問題なくコンパイルできますね.

もちろん,実際に呼ばれる関数は環境によって unsigned int 版になったり unsigned long 版になったりと異なってくるわけですが,通常であればいずれの関数にも同じ処理を書くはずので,問題ないでしょう.

というわけで,autoconf を使わずともあっさり解決できました.

October 5, 2006

[C++] 符号付き整数と符号なし整数の引数オーバーロード

下のようなソースコード(ここではファイル名を test.cc とします)を書きました.

#include <cstdio>

class A
{
private:
    int b;
public:
    A(int i) { printf("%s\n", __PRETTY_FUNCTION__); }
    A(unsigned int i) { printf("%s\n", __PRETTY_FUNCTION__); }
};

int main()
{
    size_t i = 10;
    A a(i);
    return 0;
}

これを当方が使用している Gentoo Linux 上の gcc-4.1.1 上でコンパイルします.

% g++ --version
g++ (GCC) 4.1.1 (Gentoo 4.1.1)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% g++ -Wextra test.cc
% ./a.out
A::A(unsigned int)
%

コンパイルが通り,動作します.いつもこの環境でプログラムを書いていたので,「size_t はどの環境でも unsigned int と同じ型」だと思いこんでいました.しかし,Darwin-8.8.1 (Core 2 Duo の iMac 上) 上の gcc-4.0.1 でコンパイルしたところ...

% uname -srvmpio
Darwin 8.8.1 Darwin Kernel Version 8.8.1: Mon Sep 25 19:42:00 PDT 2006; root:xnu-792.13.8.obj~1/RELEASE_I386 i386 i386 iMac5,1 Darwin
% g++ --version
i686-apple-darwin8-g++-4.0.1 (GCC) 4.0.1 (Apple Computer, Inc. build 5363)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% g++ -Wextra test.cc
test.cc: In function 'int main()':
test.cc:15: error: call of overloaded 'A(size_t&)' is ambiguous
test.cc:9: note: candidates are: A::A(unsigned int)
test.cc:8: note:                 A::A(int)
test.cc:4: note:                 A::A(const A&)
%

と,怒られてしまいました.変数 i の型を size_t から unsigned int にすると,正しくコンパイルされ,最初に述べた Gentoo Linux での結果と一致します.

そういえば size_t って組み込み型じゃなかったなぁ,なんて思い出したので,プリプロセッサの出力を見てみることにします.

g++ -E test.cc | grep size_t
typedef long unsigned int size_t;
  using ::size_t;
typedef long unsigned int __darwin_size_t;
typedef long __darwin_ssize_t;
typedef __int32_t __darwin_blksize_t;
 __darwin_size_t ss_size;
 __darwin_size_t uc_mcsize;
 __darwin_size_t uc_mcsize;
(中略)
 size_t i = 10;
%

Darwin 側には long が付いてます.ちなみに Gentoo 側では以下の通り unsigned int でした.

% g++ -E test.cc | grep size_t | head -n1
typedef unsigned int size_t;
%

念のため確認しておきますと,sizeof(size_t) はどちらの環境でも 4 でした.ただ,お恥ずかしながら,size_t が unsigned int ではない 32bit 処理系も存在する,ということを今知りました.

64bit 処理系では sizeof(size_t) が sizeof(unsigned int) と等しくないので,それを利用してオーバーロードの有無を切り替えていたのですが... 今回の場合は変数のサイズでは比べられないので,何か別な基準が必要になりそうです.例えば typeid でしょうか.

% cat > test2.cc
#include <cstdio>
#include <typeinfo>
int main()
{
    if (typeid(size_t) != typeid(unsigned int))
        puts("NOT SAME");
    else
        puts("SAME");

    if (typeid(size_t) != typeid(long unsigned int))
        puts("NOT SAME");
    else
        puts("SAME");

    return 0;
}
% g++ test2.cc
% ./a.out
NOT SAME
SAME
%

うまくいきました.この判定プログラムの結果を使ってプリプロセッサを制御すれば,size_t が long unsigned int な環境でもコンパイルが通るようにできそうです(この辺の処理には GNU 定番の autoconf を使います).


Valid XHTML 1.1! Valid CSS!
© 2004-2009 ぱくちゃん.
Last modified: Tue Jan 09 03:23:42 JST 2007