ゆとりーなの日記

日記的な事を書いて行くと思はれる

世紀末プログラミングに於けるカプセル化

オブジェクト指向っていうとカプセル化って概念があるわけです。カプセル化の利点としては、下手に情報を公開して勝手にいじられて変なことが起きると困るので隠蔽しましょうって雰囲気が根底にあると思うのですが、これは型演算なクラスにも当てはまるのかって話です。

class foo {
 public:
  explicit foo(const int v) : v_(v) {}
  
  int tekitona_sosa() {
    if (!tekitona_sosa1()) {
      tekitona_sosa2();
    }
    return v_;
  }

  bool tekitona_sosa1() {
    return v_ != 0;
  }

  void tekitona_sosa2() {
    // v_が0だとクラッシュしかねない操作が!
    // でもってv_を操作
  }

  int v_;
};

こっちが所謂普通の世界のカプセル化の話です。メンバ変数を勝手にいじったり、適当な操作の呼び出し順を間違ったするとクラッシュしたり期待通りに動かなそうな危険な雰囲気が漂っています。というわけでこういうのは割とすぐにカプセル化しようと思い立って、

class foo {
 public:
  explicit foo(const int v) : v_(v) {}
  
  int tekitona_sosa() {
    if (!tekitona_sosa1()) {
      tekitona_sosa2();
    }
    return v_;
  }

 private:
  bool tekitona_sosa1() {
    return v_ != 0;
  }

  void tekitona_sosa2() {
    // v_が0だとクラッシュしかねない!
    // でもってv_を操作
  }

  int v_;
};

平和な世界が構築されやすい気がするのですが、世紀末な世界だと話が変ってきそうです。
上の世界を世紀末な雰囲気にするとこういう感じになるかと思うのですが、まぁ要するにtemplate の部分が引数渡すコンストラクタ、T type;がメンバ変数で内部クラスがメンバ関数ですね。

template <typename T>
class foo {
  struct tekitona_sosa {
    typedef typename boost::mpl::if_<tekitona_sosa::type, tekitona_sosa2::type, T>::type type;
  };

  struct tekitona_sosa1 {
    typedef typename boost::mpl::if_<std::is_same<T, int>, std::true_type, std::false_type>::type type;
  }

  struct tekitona_sosa2 {
    typedef typename hogehage<T>::type type;
  }

  T type;
};

まずメンバ変数的なサムシングは外から書き換えることが出来ないので放置しておいても問題なさそうな空気が漂ってます。また、普通の世界ではメンバ変数をprivateにしてアクセッサ経由にすると内部実装の変更に対して強くなる(例えば値の有効性チェックを持たせるとするとメンバ関数にせざるを得ず、元がメンバ変数だとアクセス構文が変わって呼び出し側の変更の手間が以下略となるが最初からアクセッサにしておくと内部実装が変わってもアクセス構文が変らないからおいしいってあれです。)ってメリットがあったりするわけですが、世紀末な世界だと、内部実装を変えてもアクセス構文は変えなくて済んでしまうのです。

#include <type_traits>
 
template <typename T>
struct hoge1 {
  typedef T type;
};
 
template <typename T>
struct check_type {
  static_assert(std::is_integral<T>::value, "error");
  typedef T type;
};
 
template <typename T>
struct hoge2 {
  typedef typename check_type<T>::type type;
};
 
int main() {
  hoge1<int>::type a;
  hoge2<int>::type b;
  return 0;
}

でもって、メンバ関数的なサムシングも、別に内部実装関数みたいなものを放置していても特に使われても害がない気がするんですね。というのも間違った使い方をすると殆どの場合コンパイルエラーになるか、特に何も起きないからです。普通の世界だと内部実装関数を公開しておくと適当な呼ばれ方してクラッシュするとか起きかねないのでそれと比べると有害性がすくないのかなという気がするのです。
なぜこんなことを気にするのかというと、typedef系統はクラスに於けるメンバ変数等の扱いと違って、使う場所から見えてないといけないんです。つまりprivateに置こうとすると、

template <typename T>
class foo {
  struct tekitona_sosa1 {
    typedef typename boost::mpl::if_<std::is_same<T, int>, std::true_type, std::false_type>::type type;
  }

  struct tekitona_sosa2 {
    typedef typename hogehage<T>::type type;
  }

  T type;

 public:
  struct tekitona_sosa {
    typedef typename boost::mpl::if_<tekitona_sosa::type, tekitona_sosa2::type, T>::type type;
  };
};

としなくてはいけないのがちょっと嫌なんですね。やっぱり使う側が一番気になるpublic部を上に持ってきたいってのがあるのです。一方でprivateにするメリットとしては、公開するものを必要最低限なものにすることで使う側が迷いにくくなるってことはあると思うので、どうしようか迷うところです。