Mesoscopic Programming

タコさんプログラミング専門

マクロ定義されたトークンを文字列に変換するマクロ

Visual C++ 2010 Express 使ってるんですけど、
関数名とかは __FUNCTION__ とかで最初から文字列として定義されてるのに、
プロジェクト名とかは定義されてないんですよね。

ソリューション名とかプロジェクト名をソースコード中で使いたいとき、どうすれば良いんですか?ってんで、
とりあえずプロジェクトのコンパイルオプションプロパティで、Project=$(ProjectName)ってしたんですよ。

これを
#define STRING( token ) #token
的なやり方で文字列化すれば良いんじゃね?とか思ったんですよ。

でもこのやり方だと、STRING( Project )ってやると
"Project"っていう文字列が返ってきちゃうわけなんですよ。

こういう場合は、実はもう1回展開してやる必要があるみたいなんですね。なので
#define MACRO_STRING( macro ) STRING( macro )って定義して
MACRO_STRING( Project )ってやると、やっとプロジェクト名が文字列として取り出せるわけなんですよ。

これに気付くまでアホみたいに何時間もかかっちゃいました。

JanssonでJSONオブジェクトを並び順に表示するには

Janssonのjson_file_load()関数でJSONファイルを読み込むと、
JSONオブジェクトの中身は、ハッシュテーブル処理の関係ででたらめになりまが、
これを何とかJSONファイルの元の並び順にする方法を見つけましたので報告します。

それはjansson2.5のdump.cの中にやり方が書いてありました。
どおもJSONオブジェクト構造体にシリアル番号らしきメンバ変数があるので、
それを元にソートしてやれば良いみたいです。

// オブジェクトキー配列を作成します。
json_t * json = reinterpret_cast< json_t * >( const_cast< json_object_t * >( pJsonObject ) ), * value;
size_t   size = json_object_size( json );
struct object_key
{
    size_t       serial;
    const char * key;
} * keys = new struct object_key[ size ];

memset( keys, 0, sizeof( struct object_key ) * size );

// JSONオブジェクトを巡回します。
size_t index = 0;

for ( void * iter = json_object_iter( json ); iter; iter = json_object_iter_next( json, iter ), index++ )
{
    // シリアル値とキー文字列ポインタを設定します。
    keys[ index ].serial = container_of( iter, hashtable_pair, list )->serial;
    keys[ index ].key    = json_object_iter_key( iter );
}

// JSONオブジェクトをシリアル値でソートします。
for ( index = 0; index < size - 1; index++ )
{
    for ( size_t index2 = index + 1; index2 < size; index2++ )
    {
        if ( keys[ index ].serial > keys[ index2 ].serial )
        {
            struct object_key tmp = keys[ index ];

            keys[ index  ] = keys[ index2 ];
            keys[ index2 ] = tmp;
        }
    }
}

// オブジェクトキー配列を巡回します。
for ( index = 0; index < size; index++ )
{
    // オブジェクト名を表示します。
    printf( "object[ %u ] : %s\n", index, keys[ index ].key );

    // オブジェクト値を表示します。
    value = json_object_iter_value( json_object_key_to_iter( keys[ index ].key ) );

    PrintJsonValue( value );
}

// オブジェクトキー配列を削除します。
delete[] keys;

詳細はこちらをご覧ください。
JSONファイルサンプル#1

const 仮想関数の注意事項

ついこの間まで正常に動いていた関数が、気が付いたら動かなくなっていた。
なんでだろ?と思ってよくよく調べたら、
継承元の関数に const を付けたときに継承先のオーバーロード関数に const を付け忘れていたからだった。
継承元と継承先の同名仮想関数で const 型の違いがあったら、
コンパイラが気をきかして警告してくれれば良いのにと思った。

スコープ管理機能付きのポインタクラス

C#には最初からスコープ管理機能付きのポインタがあるみたいですがC++にはありません。そこでC++用のそれを作りました。

実は今までC++をCの高機能版程度にしか使っていなかったのでC++の奥深さや面白さを知らなかったのですが、最近もっとC++らしいプログラムが組みたいと思うようになって10年以上前に買ったC++の参考書を改めて読んでみました。そしたらC++の言語仕様の奥深さと面白さにはまりました。
C++の正式な教科書である「プログラミング言語C++第3版」は分厚くて辞書的な内容なので分かりにくく言語仕様の面白さがよく理解できませんでしたが、参考書の方は読み物としても面白い内容になっているのでC++の奥深さや面白さが良く分かりました。読んだ本は「現実的なC++プログラミング」と「Efective C++ 改訂2版」という有名なC++の参考書です。

クラス名 template< class T > class TScopePtr< T >

これがスコープ管理機能付きポインタクラスの名前です。
このクラスを使用するときはコンストラクタで new や new[] で作成したオブジェクトのポインタを渡します。
このクラスオブジェクトは自動変数として宣言してください。そうすると自動変数はC++の言語仕様としてスコープから外れたときにデストラクタが実行される仕組みになっているので、そのときにコンストラクタで与えられたオブジェクトを削除するという単純な設計になっています。
オブジェクトの型は基本データ型や独自のクラスなど何でも良いのでテンプレートクラスにしてあります。

例を示します。

// ブロックの開始
{
    // TScopePtr コンストラクタに new 演算子で作成したオブジェクトポインタを渡します。
    TScopePtr< int > p = new int;

    // TScopePtr 変数をオブジェクトポインタ型の変数として使用します。
    * p = 12345;

    DebugPrint( _T( "* p = %d\n" ), * p );
}
// ブロックの終了
// ここで TScopePtr 型変数 p が有効スコープから外れたので、
// 先ほど new 演算子で作成した int 型変数が自動的に削除されます。
解説

TScopePtr クラスは内部でオブジェクトポインタの参照回数を管理しています。
まずコンストラクタで参照回数が1に設定されます。
TScopePtr 変数自体は自動変数なのでスコープから外れると自動的にデストラクタが実行されます。デストラクタではオブジェクトポインタの参照回数をデクリメントします。そして参照回数がゼロになったときオブジェクトを削除します。

次に TScopePtr クラスのコピーについて説明します。
TScopePtr 変数を他の TScopePtr 変数にコピーした場合は、コピー先のオブジェクトポインタにコピー元のオブジェクトポインタをコピーしてその参照回数をインクリメントします。
そうするとコピー元の TScopePtr 変数がスコープから外れて消滅しても参照回数はゼロにならないためオブジェクトは削除されません。コピー先の TScopePtr 変数が消滅したときには参照回数がゼロになるのでオブジェクトが削除されます。
このようにして異なるブロック間でのスコープ管理機能付きオブジェクトポインタの共有が可能になります。

また TScopePtr 変数をなるべく素のオブジェクトポインタと同じように使用できるようにするためにいくつかの演算子オーバーロード関数を定義してあります。
T *(キャスト)演算子はオブジェクトポインタを返します。
*(アステリスク)演算子はオブジェクトの参照型を返します。
->(アロー)演算子はオブジェクトポインタを返すので、オブジェクトのメンバに直接アクセスできます。
[](配列添え字)演算子はオブジェクトの配列要素の参照型を返します。

ちなみに DebugPrint() は OutputDebugString() を書式付き文字列対応にしたデバッグコンソール出力関数です。

配列オブジェクトの場合

配列オブジェクトの場合はコンストラクタTArrayPtr クラスオブジェクトを渡します。
TArrayPtr クラスは配列ポインタと配列要素数だけを持つ単純なクラスです。
当初は TScopePtr クラスに配列ポインタを直接渡す方向で設計していたのですが、結局こっちの方が実装がスマートになるのでこのようにしました。

例を示します。

const size_t uElements = 3; // 配列要素数

// ブロックの開始
{
    // TScopePtr コンストラクタに TArrayPtr クラスオブジェクトを渡します。
    TScopePtr< int > p = TArrayPtr< int >( uElements );

    // TScopePtr 変数をオブジェクトポインタ型の変数として使用します。
    for ( size_t n = 0; n < uElements; n++ ) p[ n ] = n;

    for ( size_t n = 0; n < uElements; n++ ) DebugPrint( _T( "p[ %u ] = %d\n" ), n, p[ n ] );
}
// ブロックの終了

次に多次元配列の場合の例を示します。

// 多次元配列型を定義します。
const size_t uElements1 = 3;
const size_t uElements2 = 4;
const size_t uElements3 = 5;

typedef int ( TMyArray[ uElements1 ][ uElements2 ][ uElements3 ] );

// ブロックの開始
{
    // 多次元配列を単純配列として TScopePtr コンストラクタに TArrayPtr クラスオブジェクトを渡します。
    TScopePtr< int > p = TArrayPtr< int >( uElements1 * uElements2 * uElements3 );

    // TScopePtr 変数をオブジェクトポインタ型の変数として使用します。
    for ( size_t n1 = 0; n1 < uElements1; n1++ )
    {
        for ( size_t n2 = 0; n2 < uElements2; n2++ )
        {
            for ( size_t n3 = 0; n3 < uElements3; n3++ )
            {
                // 多次元配列型にキャストします。
                TMyArray & data = reinterpret_cast< TMyArray & >( * static_cast< int * >( p ) );

                data[ n1 ][ n2 ][ n3 ] = ( n1 * uElements2 + n2 ) * uElements3 + n3;
            }
        }
    }

    for ( size_t n1 = 0; n1 < uElements1; n1++ )
    {
        for ( size_t n2 = 0; n2 < uElements2; n2++ )
        {
            for ( size_t n3 = 0; n3 < uElements3; n3++ )
            {
                TMyArray & data = reinterpret_cast< TMyArray & >( * static_cast< int * >( p ) );

                DebugPrint( _T( "data[ %u ][ %u ][ %u ] = %d\n" ), n1, n2, n3, data[ n1 ][ n2 ][ n3 ] );
            }
        }
    }
}
// ブロックの終了
解説

TArrayPtr クラスに配列要素数だけを渡すと自動的に new[] 演算子で新規配列オブジェクトを作成します。

int * data = new int[ uElements ];

TScopePtr< int > p = TArrayPtr< int >( data, uElements );

のようにすれば作成済みの配列ポインタを渡すこともできます。
但し配列ポインタには必ず new[] 演算子で作成したものを渡してください。

生成消滅演算子管理クラスについて

TNewDelete クラスは生成消滅演算子管理クラスです。
グローバル new, delete, new[], delete[] 演算子オーバーロードしてその記録を行っています。
TScopePtr クラスに渡されるオブジェクトポインタのエラーチェックのために実装していますが、もしエラーチェックをしないのであれば必要のないクラスです。
TScopePtr クラスに渡されたオブジェクトポインタが new または new[] 演算子で作成されたものかどうかをチェックしています。もしそうでなければ例外を発行します。
もしオリジナルクラスで生成消滅演算子オーバーロードする場合は TNewDelete クラスに登録および削除を行ってください。

例を示します。

template< class T >
class TMyClass
{
private :

    T m_objectA; ///< オブジェクトA
    T m_objectB; ///< オブジェクトB

public :

    /// new 演算子オーバーロード関数
    static void * operator new( size_t uSize )
    {
        // メモリを確保します。
        void * pBuffer = malloc( uSize );

        // 生成消滅演算子管理クラスの単一オブジェクトリストに登録します。
        TNewDelete::AppendSingle( pBuffer, uSize );

        // メモリポインタを返します。
        return pBuffer;
    }

    /// delete 演算子オーバーロード関数
    static void operator delete( void * pBuffer )
    {
        // 生成消滅演算子管理クラスの単一オブジェクトリストから削除します。
        TNewDelete::DeleteSingle( pBuffer );

        // メモリを解放します。
        free( pBuffer );
    }

    /// new[] 演算子オーバーロード関数
    static void * operator new[]( size_t uSize )
    {
        // メモリを確保します。
        void * pBuffer = malloc( uSize );

        // 生成消滅演算子管理クラスの配列オブジェクトリストに登録します。
        TNewDelete::AppendArray( pBuffer, uSize );

        // メモリポインタを返します。
        return pBuffer;
    }

    /// delete[] 演算子オーバーロード関数
    static void operator delete[]( void * pBuffer )
    {
        // 生成消滅演算子管理クラスの配列オブジェクトリストから削除します。
        TNewDelete::DeleteArray( pBuffer );

        // メモリを解放します。
        free( pBuffer );
    }

    /// コンストラクタ
    TMyClass( const T & objectA = 0, const T & objectB = 0 ) : m_objectA( objectA ), m_objectB( objectB ) {}

    /// デストラクタ
    ~TMyClass() {}

    /// オブジェクトA参照アドレス取得関数
    T & A() { return m_objectA; }

    /// オブジェクトB参照アドレス取得関数
    T & B() { return m_objectB; }
};

ソースファイル

ソースコードのコメントは Doxygen 仕様で書いてあるので Doxygenドキュメントを自動生成できます。
コンパイラソースコードの文法エラーを検出してくれますがコメントの不具合は検出しません。しかし Doxygen を使うと生成されたドキュメントを確認することでコメントの不具合が非常に分かりやすくなります。またコメントの不具合を修正していると、そこでソースコードのバグを発見することもあります。

あと参考書を読んで初めて知ったのですが、C++ではポインタに NULL を使っちゃだめらしいです、マナーとして。NULL は void * なので結局ほとんどのケースで型変換が必要になるから無意味だそうです。なので今回のソースコードでは NULL を使いたくなる部分をすべて 0 にしてあります。

以下に全ソースファイルを示します。

  1. main.h
  2. main.cpp
  3. Debug.h
  4. Debug.cpp
  5. TNewDelete.h
  6. TNewDelete.cpp
  7. TScopePtr.h
  8. TScopePtr.cpp

このソースコードMicrosoft Visual C++ 2010 Express で検証しました。

ウィンドウズ分割窓実験プログラム

ウィンドウズ学習プログラム #001:分割ウィンドウコンテナ

はじめに

仕事でC#を勉強することになり、家でもC#をやろうと思いました。
がしかし会社のPCはWindows7なのに対し、うちのはVistaなのでVisual Studio 2012 Expressが動きません。
動かないんならインストールできないようにしてくれれば良いようなものを。
さんざん時間かけてインストールしたのに、Windows7以降でないと動かないと言われてもがっかりです。
まとにかく、しょうがないのでVisual Studio 2010 Express版のC#を入れました。
ついでに比較用としてVisual C++ 2010 Expressも入れました。
そしたら前に作ったソースコードが、エラー出まくりで修正することになりました。
そんなこんなでとりあえず分割ウィンドウが出来たのでアップしときます。
Visual C++ 2010 Expressの空プロジェクトに、以下のソースを追加すればビルドできます。


実行画面


実行ファイル
  1. Windows3.zip


メモ:これが安全というものなのか?_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES の功罪

私は今しがた _CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES の存在を知った。
どうやっても _tcscpy_s() がエラーを起こすからだ。
何故なのだ?
最近 Visual C++ 2010 Express を始めて、前のソースが警告だらけになるので _tcscpy() などを _tcscpy_s() のように "_s" 付きに変えたら動かなくなった。
この野郎ふざけるなと思って、もしかしてマイクロソフトの陰謀かと疑いもしたが、ヘルプを調べてやっと分かった。
_CRT_SECURE_CPP_OVERLOAD_SECURE_NAMES が 1 に定義されてる時は "_s" を付けても引数の数は "_s" なしと一緒なのだそうだ。
もっと安全な関数に変えろと警告を出しといて、ちゃんと "_s" 付きのやつに変えたのにエラーで止まるとは逆に危険になったではないか。
だったら同時に、でも引数の数は変えなくても良いよって教えてくれても良さそうなものだ。
まあ直ったから良いけど。