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の結果が違うことが分かります。ちゃんと期待通りに動いてくれました。