Home

zakio.net

法則に違反したっていいじゃない

  • Posted by: zakio
  • 2009年7月22日 11:10
  • C++ | OOP

オブジェクト指向プログラミングにおける有名な法則の一つにデメテルの法則 (Law of Demeter, LoD) というものがある。これは、簡単に言えば「戻り値として返されたオブジェクトのメソッドを呼び出してはいけない」という法則であり、例えば次のようなコードを書くと、「それ、デメテルの法則に違反しているよ」などと言われたりするので注意が必要だ。


int id = projects.Find(projectName).GetID(); // プロジェクト名から ID を取得

そして、このコードをデメテルの法則に違反しないように書き換えると、次のようになる。


int id = projects.GetID(projectName); // プロジェクト名から ID を取得

変更後のコードは Find が返すオブジェクト(ここでは多分 Project みたいなクラス)に依存していないので、オブジェクト間の結合度が下がったことになる。結合度の低いプログラムは良いプログラムであり、デメテルの法則に従えば良いプログラムになるというわけだ。

でも、ちょっと待って欲しい。

続きを読む

仮想関数を generic にする

C++では、メンバ関数テンプレートを仮想関数にできない。ところが、C++/CLI ではテンプレートに良く似た機能である generic を仮想関数につけることができる。これにはどんなトリックが隠されているのだろう?

まずは、次のようなコードから。


public ref class Base // ref class なので参照型
{
public:
  generic <typename T> // generic を仮想関数につける
  virtual int Func(T in) { // T は任意の型
      return sizeof(in); // T 型のサイズを返す(本当は size_t だけど)
  }
};

public ref class Derived: public Base
{
public:
  generic <typename T>
  virtual int Func(T in) override { // Base::Func を override
      return sizeof(in) * 2; // T 型のサイズを倍にして返す
  }
};

これはTがどんな型であっても正しく動作する。Derived のインスタンスを Base のハンドルで参照しても、Derived 側の Func が呼ばれる。完璧だ。

C++ では、Tの型が決まらないと仮想関数テーブルを構築できず、Tの型を知るためには全ての翻訳単位を調べなければならない。よって、このようなメンバ関数は禁止されている。そして、以前のエントリでは「なんだ、その都度 JIT コンパイルすればいいじゃん」という結論で終わっていた。今回は、これをもう少し調べてみようと思う。

続きを読む

Pimpl イディオムのお手軽な実装

  • Posted by: zakio
  • 2009年4月15日 22:21
  • C++

Pimpl イディオム(Handle-Body, Compilation or Compiler Firewall, Cheshire Cat とも呼ばれたりする)は実装の詳細を隠蔽したい時に用いられる手法だが、内部が委譲の嵐になったり、ポインタの扱いに注意を払う必要があったりと、気軽には使い難いパターンの一つではある。内部の委譲に関しては Pimpl の性質上仕方がないものとして諦められるが、せめてポインタ周りだけでもお手軽に扱えるようにならないだろうか、というのが今回のお題だ。

教科書に載っているような Pimpl の実装は、大抵の場合、次の例の前半部分まで。こうやって隠蔽した Impl のポインタ pImpl_ を正しく扱うためには、コンストラクタ、デストラクタ、コピーコンストラクタ、代入演算子を「正しく」実装しなければならない。


class PimplTest {
public:
  // ... 外部に公開するインタフェース

private:
  class Impl; // 実装の詳細は Impl に隠蔽し、
  Impl* pImpl_; // そのポインタを保持する

// Impl を正しく扱うためには、以下のコードを「正しく」書かなければならない
public:
  PimplTest(); // ここで new
  ~PimplTest(); // ここで delete
  PimplTest(const PimplTest& obj); // Impl に対する深いコピーが必要
  PimplTest& operator = (const PimplTest& rhs); // こちらも同様に深いコピー
};

時々、ポインタ周りを楽にするために shared_ptr を使って Pimpl イディオムを実装している例を見かけるが、これではコピーや代入時に実装の詳細が共有されてしまう。コピーや代入を禁止しても構わない場合ならこれで充分かも知れないが、そうでない場合は自前のコピーコンストラクタや代入演算子を用意する必要があるため、やはり、お手軽とは言い難いだろう。

続きを読む

コレクションの永続化

.NET Framework で独自のコントロールを作りたい場合は、UserControl から派生させたクラスを用意すればよい。一般的に、自作コントロールには独自のプロパティも含まれており、そのプロパティが自作クラスになっていることも多いはず。今回は、自作クラスの配列をプロパティとして持たせたところ大ハマリしたという話。

ある自作コントロールに自作クラス MyItem の配列をプロパティとして持たせたいと思い、System::Collections::Generic::List<MyItem^>^ 型のプロパティ MyItemList を追加することにした。cli::array ではなく List を使っているのは vector っぽく使いたいためだが、この違いは本質ではない(と思う)。悲劇の始まりは次のようなコードからだ。


ref class MyItem;
ref class MyCtrl : public System::Windows::Forms::UserControl
{
// ... 色々あって、

private:
  System::Collections::Generic::List<MyItem^>^ m_itemList; // リストの実体

public:
  MyCtrl(void) {
      InitializeComponent();

      // コンストラクタで m_itemList を確保する
      m_itemList = gcnew System::Collections::Generic::List<MyItem^>;
  }

public:
  property System::Collections::Generic::List<MyItem^>^ MyItemList { // このプロパティは
      System::Collections::Generic::List<MyItem^>^ get() { // get だけしか持っていない(set は許可しない)
          return m_itemList; // 中身は自由にしても構わないが、リストそのものはコチラで管理させてくれ
      }
  }

// ... 色々あるよね
};

続きを読む

C++でメンバ関数テンプレートを仮想関数にできないのは何故?

  • Posted by: zakio
  • 2009年3月19日 21:44
  • C++

C++ でメンバ関数テンプレートを仮想関数にしようとして、コンパイラに怒られた経験がある人は多いと思う。


class Hoge {
public:
  template <typename T>
  virtual void Func(const T& in) {} // メンバ関数テンプレートは仮想関数にできない
};

こういう場合、クラスを丸ごとテンプレート化してしまえば何とかなる場合が多いので、どうしてもという時は設計を見直すことになるだろう。こうすれば仮想関数でテンプレート引数の型が使えるようになる。


template <typename T> // テンプレートクラスにすれば、仮想関数で T が使える
class Hoge {
public:
  virtual void Func(const T& in) {}
};

続きを読む

Blender で favicon

  • Posted by: zakio
  • 2009年3月11日 02:52
  • Blender

blender.pngBlender で favicon を作ってみた。

Blender はフリーの 3DCG ソフト。操作方法にクセがありまくるため毎回イライラしながら作ることになるのだが、icon 系の画像を作る時にはナンダカンダ言いつつも結局 Blender を使っていることが多い。

続きを読む

値型とボックス化

  • Posted by: zakio
  • 2009年3月 4日 13:24
  • C++/CLI

前回の続き。

CLI は値型と参照型を区別する。一般に値型はスタック、参照型はヒープ上に配置されるが、C++/CLI では参照型であっても C++ と同じようにスタックに置くことができる。正確には「置いたように見せかける」だが、構文的にも意味的にもスタックに置いたオブジェクトと同じように扱える。


List<int> list1; // 参照型なのにスタックに置ける(ように見える)
List<int>* list2 = &list1; // コンパイルエラー: CLI heap 上にあるオブジェクトはポインタで参照できない
List<int>& list3 = list1; // コンパイルエラー: CLI heap 上にあるオブジェクトはトラッキング参照を用いること
List<int>^ list4 = %list1; // % でハンドルを取得
List<int>% list5 = list1; // トラッキング参照は & と同様の振る舞い

構文としてはスタックだが、参照型はあくまで参照型。実際のオブジェクトは CLI ヒープ上に存在するため、間接的に参照する場合はポインタではなくハンドルを使うことになる。つまり * → ^, & → % のルールで自然に振舞うということ。ここまでは問題ない。混乱の元は値型だ。

続きを読む

ハンドルとポインタと参照

  • Posted by: zakio
  • 2009年2月25日 21:56
  • C++/CLI

C++ に慣れ親しんだ私は、C++/CLI のハンドルを CLI heap 用のポインタという風に理解している。native heap は new で確保してポインタへ、CLI heap は gcnew で確保してハンドルへという単純な話だ。それだけならわざわざ取り上げる必要もないが、色々と調べているうちに混乱してきたので書きながら整理してみようと思う。

まずは単純な話から。


using namespace std;
using namespace System::Collections::Generic;

vector<int>* pVec = new vector<int>; // native heap を new で確保して pointer で受ける
List<int>^ hList = gcnew List<int>; // CLI heap を gcnew で確保して handle で受ける

// -> は同じ
pVec->push_back(1);
hList->Add(1);

// * も同じ
(*pVec).push_back(2);
(*hList).Add(2);

// delete も同じ
delete pVec;
delete hList;

vector に対応するものが List という点が気持ち悪いが、まぁこれはそういうもの。大事なのは new → gcnew, * → ^ という対応の部分だ。一旦領域を確保してしまえば、後は C++ と同じように扱える。

続きを読む

ソースコードを美しく

といっても、読みやすいきれいなソースコードを書きましょうといった話ではなく、blog 上でソースコードをきれいに表示したいという話。

気になったことを備忘録の感覚で書き留めていこうという趣旨で blog を始めたのだが、気になることの大半がソフトウェアに関する事なので、ソースコードを簡単にコピペできることは「めんどくさくない」運用という点においては必須の機能。

とりあえずは、以下のようなコードが普通にコピペできれば合格だ。

template <typename T>
class Hoge {
    T m_val;
public:
    explicit Hoge(const T& val = T()): m_val(val) {}
    const T& GetVal() const {
        return m_val; // 持ってる値を返すだけ。意味も無くコメントを書いているのは、一行を無理やり長くするためだったりする。
    }
};

続きを読む

blog あるいは備忘録

とりあえずテスト。

こんなイイ加減なエントリーで始まるブログも珍しいと思うが、極度に飽きっぽい私にとっては、これくらいから始めた方が長続きするはず。

最近(に限った話ではないかも知れないが)物忘れが激しくなってきた気がするので、気になったことは記録しておいた方がいいかも、というのがブログを始めようと思った動機。ただし、記録という作業そのものに手間がかかってしまうとあっという間に終了フラグが立ってしまう。

そこで、ブログ界のデファクトスタンダードである(らしい) Movable Type を導入し、このあたりの「めんどくささ」を取り除いてみようという試みだが、果たしてうまくいくことやら......。

  • Comments (Close): 0
  • TrackBack (Close): 0

Index of all entries

Home

Search
Feeds

Return to page top