2013年1月4日金曜日

typedefのおさらい

C#のプロパティような振る舞いを実装するで関数ポインタをtypedefで定義したのですが少し手間取りました。

///
/// 符号あり16ビット長をINT16と定義する
///
typedef signed short INT16

「signed short」を「INT16」に定義するとずっと思い込んでいたのが間違いでした。正確には「typedef <型宣言>」のようで関数ポインタを定義するには次のようにすれば良いようです。

///
/// プロパティクラスの宣言の一部
///
template<typename H, typename T>
class Property
{
 typedef T (H::*GET)(void) const;
 typedef void (H::*SET)(const T&);
 
 H* _host;
 GET _get;
 SET _set;
};

このように定義するとGETとSETという名前で関数ポインタを扱うことができるようになります。

C#のプロパティような振る舞いを実装する

C#のgetter/setterのようなことがC++でもやりたくなりました。
メンバ変数とメンバ関数の経由部分をテンプレートクラスで実装します。

プロパティ機能を提供するテンプレートクラス
///
/// Property.h
///
#ifndef _INC_PROPERTY_H_
#define _INC_PROPERTY_H_

namespace Test {

template<typename H, typename T>
class Property
{
 typedef T (H::*GET)(void) const;
 typedef void (H::*SET)(const T&);

 H* _host;
 GET _get;
 SET _set;

 ///
 /// _hostに代入する術がないのでガード
 ///
 Property(const Property<H, T>& other);

public:
 Property(H* host, GET get, SET set);
 ~Property();
 operator T() const;
 Property<H, T>& operator=(const T& val);
 Property<H, T>& operator=(const Property<H, T>& other);
};

template<typename H, typename T>
Property<H, T>::Property(H* host, GET get, SET set)
 : _host(host), _get(get), _set(set)
{
}

template<typename H, typename T>
Property<H, T>::~Property()
{
}

template<typename H, typename T>
Property<H, T>::operator T() const
{
 return (this->_host->*_get)();
}

template<typename H, typename T>
Property<H, T>& Property<H, T>::operator=(const T& val)
{
 (this->_host->*_set)(val);
 return *this;
}

template<typename H, typename T>
Property<H, T>& Property<H, T>::operator=(const Property<H, T>& other)
{
 (this->_host->*_set)((other._host->*_get)());
 return *this;
}

}
#endif

getterとsetterは静的メンバ関数でないので関数ポインタでアクセスする場合、オブジェクトを指定する必要があります。どのオブジェクトのアクセッサを使うか判別するため、事前に_hostにオブジェクトを登録しておきます。コピーコンストラクタが発動した場合、_hostにオブジェクトを代入することが出来ないのでprivateにして使えないようにしています。

プロパティテンプレートクラスの使い方の例
///
/// 座標情報を格納するPointクラスのX座標とY座標をプロパティテンプレートで実装する
/// Point.h
///
#ifndef _INC_POINT_H_
#define _INC_POINT_H_

#include "Property.h"

///
/// 座標を格納するクラス
///
class Point
{
 int _x;
 int _y;

 void setX(const int& val);
 int getX() const;
 void setY(const int& val);
 int getY() const;

public:
 Test::Property<Point, int> X;
 Test::Property<Point, int> Y;

 Point();
 Point(const Point& other);
 void operator=(const Point& other);
};

#endif

///
/// Point.cpp
///
#include "Point.h"

void Point::setX(const int& val)
{
 this->_x = val;
}

int Point::getX() const
{
 return this->_x;
}

void Point::setY(const int& val)
{
 this->_y = val;
}

int Point::getY() const
{
 return this->_y;
}

Point::Point()
 : X(this, &Point::getX, &Point::setX)
 , Y(this, &Point::getY, &Point::setY)
{
}

Point::Point(const Point& other)
 : X(this, &Point::getX, &Point::setX)
 , Y(this, &Point::getY, &Point::setY)
{
 *this = other;
}

void Point::operator=(const Point& other)
{
 this->X = other.X;
 this->Y = other.Y;
}

///
/// main.cpp
///
#include <iostream>
#include "Point.h"

int _tmain(int argc, _TCHAR* argv[])
{
 Point a;
 Point b = a;

 a.X = 1;
 a.Y = 2;
 b.X = 123;
 b.Y = 456;

 std::cout << "a.X = " << a.X << std::endl;
 std::cout << "a.Y = " << a.Y << std::endl;
 std::cout << "b.X = " << b.X << std::endl;
 std::cout << "b.Y = " << b.Y << std::endl;

 return 0;
}

メンバ変数にはメンバ変数のアクセッサを経由してアクセスするようにします。さらにアクセッサをPropertyクラスで隠蔽することであたかも変数だけで操作しているように見せかけます。
コピーコンストラクタの実装では、プロパティのコピーコンストラクタではなく、コンストラクタを呼び出した後にoperator=で値の代入を行います。この部分が冗長なので恰好悪いのですね。ここでthisポインタを渡す方法があれば解決できるんですが、思いつきませんでした。

出力結果
a.X = 1
a.Y = 2
a.X = 123
b.Y = 456

Point b = aのところでコピーコンストラクタが発動しますが、aとbの結果が違うことが分かります。ちゃんと期待通りに動いてくれました。