CodeZine(コードジン)

特集ページ一覧

C++でプロパティを実現するココロミ

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2014/01/28 14:00

 昨年末からC++/CXで遊んでいます。C++/CXは"ほぼC++"ではありますが、C++にはない機能も散見されます。GC(garbage collection)のおかげでnewしたものの後始末は勝手にやってくれますし、あらゆるものがPlatform::Objectから(直接/間接的に)導出されているのも特徴の一つです。今回のお題はC++にはない機能の一つ:プロパティをC++で実現したらどうなるの、ってオハナシ。

目次

C++/CXのプロパティ

 C++/CXあるいはC++/CLI、C#、VB.NETのプロパティとは、あたかも変数であるかのようにふるまう関数です。例えば:

list-01
using namespace Platform;

ref class Person sealed {
public:

  property String^ Name {
    String^ get();
    void    set(String^ value);
  }

  property int Age {
    int  get();
    void set(int value);
  }

private:
  String^ name_;
  int     age_;
};

String^ Person::Name::get()              { return name_; }
void    Person::Name::set(String^ value) { name_ = value; }

int     Person::Age::get()               { return age_; }
void    Person::Age::set(int value)      { age_ = value; }

 このように、property名前に対しget/set関数を定義することで、あたかもメンバ変数であるかのごとくふるまいます。

list-02
int main(Platform::Array<Platform::String^>^) {
  Person man;

  // メンバ変数への値の代入のようにふるまう
  man.Name = L"Adam"; 
  man.Age  = 21;

  // メンバ変数の持つ値を読みだしているかのようにふるまう
  String^ name = man.Name;
  int     age  = man.Age;

  Details::Console::WriteLine(L"Name : " + name);
  Details::Console::WriteLine(L"Age  : " + age.ToString());
}

 man.Name、man.Ageが左辺値と解釈されるならset(),右辺値と解釈されるならget()が呼び出されます。変数の読み書きに見せかけた関数の呼び出しです。これをC++でも実現した例がないかと探してみたら、ありました。C++標準員会:SC22/WG21にプロポーザル(提案書):N1615として提出されていました。十数ページの小さなドキュメントですし、英語の勉強がてら読んでみるのもよいでしょう。N1615はプロパティを言語仕様の拡張によってではなく、ライブラリで実現する方法を提案しています。そこにはこんな小さなコードがありました。

list-03
/* excerpt from
 *   SC22/WG21/N1615 "C++ Properties -- a Library Solution" 2004-04-09
 */
template<class T, class Object,
    typename T(Object::*real_getter)() const,
    typename T(Object::*real_setter)(T const&)>
class RWProperty {
    Object* my_object;
public:
    void operator()(Object* obj) { my_object = obj; }

    T get() const { return (my_object->*real_getter)(); }
    T set(T const& value) { return (my_object->*real_setter)(value); }

    T operator()() const { return get(); }
    T operator()(T const& value) { return set(value); }

    operator T() const { return get(); }
    T operator=(T const& value) { return set(value); }

    typedef T value_type;
};

 このRWPropertyを使って作られたPersonがコチラ。

list-04
#include <iostream>
#include <string>

class Person {
private:
  int age_;
  // getter/setter for age
  int get_age() const { return age_; }
  int set_age(int const& value) { return age_ = value; }

  std::string name_;
  // getter/setter for name
  std::string get_name() const { return name_; }
  std::string set_name(std::string const& value) { return name_ = value; }

public:
  Person() { Age(this); Name(this); /* do this before use! */ }

  // property age/name
  RWProperty<int,         Person, &Person::get_age,  &Person::set_age > Age;
  RWProperty<std::string, Person, &Person::get_name, &Person::set_name> Name;

  ostream& print_on(std::ostream& out) const { return out << name_ << ':' << age_; }
};

std::ostream& operator<<(std::ostream& stream, const Person& p) {
    return p.print_on(stream);
}

using namespace std;

int main() {
    Person adam;
    adam.Name = "Adam";
    adam.Age  = 20;
    cout << adam << endl;
    string name = adam.Name;
    int    age  = adam.Age;
    cout << name << ':' << age << endl << endl;
}

 ...なるほど。プロパティの型とプロパティの持ち主の型、そしてget/setするメンバ関数をテンプレート引数に与え、持ち主のコンストラクタ内でthisで初期化しておけば、man.Ageが右辺値ならoperator int(),左辺値ならoperator=(int const&)の中から(テンプレート引数で与えておいた)get/set関数が呼び出されるってカラクリですな。関数へのポインタはコンパイル時に決定する定数なのでテンプレート引数に与えることができ、RWPropertyのメンバは持ち主のポインタだけで済ませています。

 このプロポーザル、2004年に提出されています。2004年というとC++03が決まったばかり。テンプレートの仕様も今ほどにはきっちり定まっていない時期としては妥当な実装ではないかと思います。要はプロパティが右辺/左辺に現れたとき、あらかじめ用意しておいた関数が呼ばれればいい。この実装をお手本に"イマ風"に書き直してみましょうか。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • επιστημη(エピステーメー)

    C++に首まで浸かったプログラマ。 Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"り わんくま同盟でたまにセッションスピーカやったり 中国茶淹れてにわか茶人を気取ってたり、 あと Facebook とか。 著書: - STL標準...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5