C++でgetter/setterをマクロで宣言・定義してしまう

この記事は1年以上前に掲載されたものです。 情報が古い場合がありますのでお気を付け下さい。

ここではC++を使って開発を行っている方向けにsetterとgetterを簡単に記述するマクロを伝授したい。特にクラスでメンバー変数の値を取得したりセットしたりするような方にとってはかなり重宝するだろう。なお、この方法はcocos2d-xでも使われているテクニックであるため、cocos2d-xを使っている方はそちらも是非確認してみたい。

宣言だけを行い、実際の処理は別の場所で行う場合

まず、以下ようなヘッダーを作成する(ファイル名はなんでも可だが、ここではproperty.hとして説明する)。

[code lang=”cpp”]#ifndef HEADER_DEFINE_PROPERTY
#define HEADER_DEFINE_PROPERTY

#define DEFINE_PROPERTY_READWRITE(type, varName, funcName)
protected:
type varName;
public:
virtual type get ## funcName();
virtual void set ## funcName(type value);

#define DEFINE_PROPERTY_READONLY(type, varName, funcName)
protected:
type varName;
public:
virtual type get ## funcName();

#endif[/code]

上記のヘッダーファイルを作成した後、なんらかのクラスを定義する際に、以下のようなコードを書いてみる。

[code lang=”cpp”]#include "property.h"

class Hoge {
public:
DEFINE_PROPERTY_READWRITE(int, _bar, Bar)
DEFINE_PROPERTY_READONLY(int, _baz, Baz)
}[/code]

これで、foo.hではDEFINE_PROPERTY_READWRITE(int, _bar, Bar)の部分では、protectedの変数_barにpublicメソッドとしてgetBarとsetBarが、DEFINE_PROPERTY_READONLY(int, _baz, Baz)はprotectedの変数_bazにpublicメソッドのgetBazが宣言される。

これでDEFINE_PROPERTY_READWRITE(int, _bar, Bar)の部分はリードライト、DEFINE_PROPERTY_READONLY(int, _baz, Baz)はリードオンリーとして扱うことができるのである。

しかしながら、この方法では実際の処理が定義されていないので、このままでは動かない。単純に変数をセット・ゲットするだけなのであれば、以下の方法がある。

簡単なgetter/setterの定義も同時に行う方法

以下のヘッダーを作成する(ここではsynthesize.hとする)

[code lang=”cpp”]#ifndef HEADER_DEFINE_SYNTHESIZE
#define HEADER_DEFINE_SYNTHESIZE

#define DEFINE_SYNTHESIZED_PROPERTY_READWRITE(type, varName, funcName)
protected:
type varName;
public:
virtual type get ## funcName() {return varName;}
virtual void set ## funcName(type value) {varName = value;}

#define DEFINE_SYNTHESIZED_PROPERTY_READONLY(type, varName, funcName)
protected:
type varName;
public:
virtual type get ## funcName() {return varName;}

#endif[/code]

2015-06-30追記

一部コードに間違いがありましたので修正しました。

こちらは、前述のものとは違い、getterとsetterの定義も書いているため、ヘッダーファイルに書かなくても実際に変数が返ったり、セットしたりすることができる。念のため継承の際にオーバーライド1 することも考えているためvirtual修飾している。

ちなみに、ここでは一般的な変数型を想定しているが、テクニックや改良次第では参照型やポインタ型なども扱えるようになる。また、使いどころが難しいものの、配列と列挙子を併用して一種のデータセットのように扱うときも直接配列にアクセスせずにアクセサを使うことも可能である。

上記のマクロを有効に活用することでコードの記述コストがある程度少なくなるので、是非とも活用しよう2

後書き・その他

近年、オブジェクト指向言語ではあるクラスで定義された変数(C++ではメンバー変数、Javaではインスタンス変数/クラス変数という)を他のオブジェクトから直接アクセスするのはすべきではないことの方法の一つとなっている。

特にObjective-Cでは顕著で、他の変数にアクセスするには必ずアクセサと呼ばれるメソッドを用いて間接的に変数を操作するのが半ば常識となっている3 。C#やSwiftでも記述方法は違うもののプロパティーが使える。

しかしながら、JavaではObjective-CやC#、Swiftでいうプロパティーの概念はないため、基本的にはsetterとgetterを自分で定義しなければならない。そういう意味ではかなり面倒な作業が必要だろう。

これはC++も同様で、基本的にはgetterとsetterは自力で実装しなければならないが、マクロを使うことである程度簡潔に記述することができるのが救いとも言える。これはあくまでgetter/setterをマクロで定義しているに過ぎないが、これがあるかないかで結構変わってくると言える。

個人的にはいつかJavaやC++(C++/CLIではない)にもObjective-CやC#でいうプロパティーが実装されることを願うばかりである。

ウェブマスター。本ブログでITを中心にいろいろな情報や意見などを提供しています。ご用の方はコメントかコンタクトフォームにて。
  1. 親クラスから継承した子クラスで処理を変更すること []
  2. 単純に変数のまとめるだけなのであれば構造体を使うことで済んでしまうのであるが。また、C++においてはクラスと構造体はデフォルトのアクセス権の違いだけで基本的に同一のものとして扱われる点で要注意 []
  3. 現在ではプロパティーと呼ばれるものが用いられているが、これの実態はメソッドであり、例えばhogeというプロパティーがあった場合、getterとsetterに別の名前を指定していなければhogeというメソッドを呼べばgetterが、setHogeというメソッドを呼べばsetterが呼ばれる []
スポンサーリンク

フォローする