WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

Ruby で static メンバ変数を実現する

本日は DokuWiki による記事のテストを兼ねて、Ruby のちょっとした Tips をご紹介します。Ruby は優れたオブジェクト指向言語ですが、まれに C/C++ にある「アレ」が使えないかなぁ、と思うときがあったりします。そのひとつが C++ の static メンバ変数ではないでしょうか。C++ では static メンバ変数の使い方はけっこう重要だったりするのですが、そのノウハウを Ruby に持ってこようとするとなかなかうまくいかないことが多いです。

そこで、私なりに C++ の static メンバ変数の性質を Ruby で再現する方法を考えてみました。どれも完璧ではありませんが、うまく使い分ければだいたいの場面には対応できると思います。C++ のコードを Ruby に移植するときにも便利かもしれませんね。私も Ruby に関してそれほど多くを知っているわけではないので、もっと良い方法をご存知の場合は、ぜひ教えてください(^^)

Ruby のクラス変数

Ruby には「クラス変数」という C++ の static メンバ変数に似た機能があります。まずはそれをご紹介しましょう。クラス変数はあるクラスのすべてのインスタンスから共通にアクセスできる変数で、先頭に "@@" が付きます。

class Foo
  @@a = 1
  def a
    @@a
  end
end
 
Foo x, y
print "x.a = #{x.a}, y.a = #{y.a}\n"
# x.a = 1, y.a = 1

Ruby のクラス変数と C++ の static メンバ変数との主な違いは、だいたい以下のふたつです。

  • Ruby のクラス変数はクラス外からアクセスできない
  • Ruby のクラス変数は派生クラスでオーバーライドできない

C++ でもメンバ変数を public にするのは稀なので前者は問題にはなりませんが、後者はけっこう痛いですね。念のため例を挙げておきましょうか。C++ では派生クラスで同じ static メンバ変数を定義すると、それらは独立した変数として扱われます。

class Foo
{
public:
  static int a = 1;
};
 
class Bar : public Foo
{
public:
  static int a = 2;
};
 
int main()
{
  printf("Foo::a = %d, Bar::a = %d\n", Foo::a, Bar::a);
  // Foo::a = 1, Bar::a = 2
}

これに対して、Ruby のクラス変数は派生クラスでオーバーライドすることはできず、常に同じインスタンスを参照します。

class Foo
  @@a = 1
  def a
    @@a
  end
end
 
class Bar < Foo
  @@a = 2
  def a
    @@a
  end
end
 
print "Foo::@@a = #{Foo.new.a}, Bar::@@a = #{Bar.new.b}\n"
# Foo::@@a = 2, Bar::@@a = 2

これは変数宣言が必要ないという Ruby の仕様から導かれた必然的な相違なのでしょう。しかし、C++ では template との組み合わせで static メンバ変数のオーバーライドを多用するときがあるので、C++ 頭の人間にはちょっと不便に感じます(^^;。そんなわけで、クラス変数以外に C++ の static メンバ変数的な機能を実現できないかな、と思った次第です。

定数を使ってみる

Ruby はクラス変数のほかに定数もクラス内で定義できます。こちらは派生クラスでのオーバーライドが可能で、しかもクラス外からもアクセスできるなど、C++ でいう static const メンバ変数とほぼ同等の性質を持っています。しかし、定数ですので変更できません(実は現在の Ruby では警告が出るだけで変更できますが、非推奨です)。そこで、以下のように必要なメンバを保持するクラスを作成し、そのインスタンスを定数に代入することで変更可能な static メンバ変数を実現できます。

class Baz
  attr_accessor :a
end
 
class Foo
  Static = Baz.new
  Static.a = 1
end
 
class Bar < Foo
  Static = Baz.new
  Static.a = 2
end
 
print "Foo::Static.a = #{Foo::Static.a}, Bar::Static.a = #{Bar::Static.a}\n"
# Foo::Static.a = 1, Bar::Static.a = 2

この方法の欠点は、見てのとおりアクセス方法が少々カッコ悪いことです^^;。また、別にクラスを用意するのも少々面倒ですね。これに関しては、Symbol を添え字にした Hash を使っても良いかもしれません。

特異クラスのインスタンス変数を使ってみる

もうひとつ別の方法として、クラスインスタンスのインスタンス変数を使う方法もあります。Ruby ではクラスもオブジェクトですので、当然インスタンス変数(C++ でいう通常のメンバ変数)を持てます。さらに「特異クラス」という機構を利用して、特定のインスタンスにのみインスタンス変数を追加することも可能です。これを利用して、必要なクラスインスタンスにインスタンス変数(とアクセスメソッド)を追加して static メンバ変数を実現したのが以下のソースです。

class Foo
  class << self
    attr_accessor :a
  end
  self.a = 1
end
 
class Bar < Foo
  class << self
    attr_accessor :a
  end
  self.a = 2
end
 
print "Foo.a = #{Foo.a}, Bar.a = #{Bar.a}\n"
# Foo.a = 1, Bar.a = 2

これだとアクセス方法も自然になり、アクセスメソッドの定義方法で書き換え禁止もできるのでなかなか良い感じです。

この方法の最大の欠点は、特異クラスのインスタンス変数が派生クラスに引き継がれないことです。つまり、上記のソースで Bar の特異クラスを定義しなかった場合、"Bar.a" は nil を返してしまいます。派生クラスのアクセスメソッドで基底クラスのインスタンス変数をアクセスすればよいのですが、定義がちょっと面倒です。そのような場合は、前述の定数を使った方法のほうが適切でしょうね。

現時点で私が思いつくのはこのくらいです。私も書いていてこれが誰かの役に立つのかどうか疑問に思えてきましたが、もし使えることがあれば使ってやってください(^^ゞ。では、本日はこのへんで。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles