ikautak.log

C/C++, Python, CUDA, Android, Linux kernel, Network, etc.

More Effective C++ 項目30 プロキシークラス

新訂版 More Effective C++ (AddisonーWesley professional co)

新訂版 More Effective C++ (AddisonーWesley professional co)

  • 作者: スコット・メイヤーズ,安村通晃,伊賀聡一郎,飯田朱美,永田周一
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2007/06/29
  • メディア: 単行本(ソフトカバー)
  • 購入: 8人 クリック: 129回
  • この商品を含むブログ (43件) を見る

項目30 プロキシークラス
2次元配列を実装する

C++では、変数でサイズを指定して配列を作れない。

void processInput(int dim1, int dim2)
{
    int data[dim1][dim2];        // NG
    ...
}

int* data = new int[dim1][dim2]; // NG

クラスで2次元配列を作ると

template<class T>
class Array2D {
public:
    Array2D(int dim1, int dim2);
    ...
};

のようなものになる。配列の定義は以下のように書ける。

Array2D<int> data(10, 20);                         // OK

Array2D<float> *data = new Array2D<float>(10, 20); // OK

void processInput(int dim1, int dim2)
{
    Array2D<int> data(dim1, dim2);                 // OK
}

しかし、

cout << data[3][6];

みたいな書き方をするには、operator[ ][ ]を定義する必要があるが、そんなものはないので、
1次元配列(Array1D)を返すようなArray2Dのoperator[ ]と、1要素を返すArray1Dのoperator[ ]を実装する。

template<class T>
class Array2D {
public:
    class Array1D {
    public:
        T& operator[](int index);
        const T& operator[](int index) const;
        ...
    };

    Array1D operator[](int index);
    const Array1D operator[](int index) const;
    ...
};

これで

Array2D<float> data(10, 20);
...
cout << data[3][6]; // OK

のように書けるようになる。また、クライアントはArray1Dクラスを意識する必要もない。
Array1Dのように他のオブジェクトのために存在するオブジェクトをプロキシーオブジェクト、それを生成するクラスをプロキシークラスと呼ぶ。

operator[]によって読み出しと書き込みを区別する

文字列クラスの例。文字を表すプロキシークラスを作り、そのオブジェクトをoperator[ ]で返す。

class String {
public:
    class CharProxy {
    public:
        CharProxy(String& str, int index);

        CharProxy& operator=(const CharProxy& rhs); // lvalue。代入用
        CharProxy& operator=(char c);

        operator char() const;                      // rvalue。読み出し用

    private:
        String& theString;
        int charIndex;
    };

    const CharProxy operator[](int index) const;
    CharProxy operator[](int index);
    ...

friend class CharProxy;

private:
    RCPtr<StringValue> value;
};

そうすると、以下のような書き方をしたとき、CharProxyクラスで振る舞いを決めることができる。

String s1, s2;
...
cout << s1[5]; // CharProxyオブジェクトが読み出され、char型に変換される

s2[5] = 'x';   // CharProxyのcharを受け取るoperator=でtheStringの指定index部分を書き換える

s1[3] = s2[8]; // CharProxyのCharProxyを受け取るoperator=で書き換え。


プロキシークラスを使うと、多次元配列やoperator[ ]での読み出し・書き込みの区別、暗黙の型変換の抑制などが可能だ。
一方、プロキシーオブジェクトはテンポラリなので、作成・削除のコストがかかる。
ソフトウェアも複雑になるため、理解・メンテが大変になる。