第十一報:マルチヴァリューの扱い方2

 前回のマルチヴァリューは即日考えて即日公表したのでちょっと練りきれてないところがありました。そこでいろいろ考えて、さらに発展させてみました。前回の追記に関することも含め、詳しく話していきたいと思います。


 先ず、マルチヴァリュークラスをいちいち作ったのでも構わない、というところから話していきたいと思います。

 マルチヴァリュークラスは >> で値を取得し、1 つ数が少ないマルチヴァリューオブジェクトを返す、という風にしていました。そして、Skip で値を取得せずに同じことをし、Get で値のみを取得しました。

 前回メインで話したような 2 つのクラスでなんとかするのではなく、複数のクラステンプレートを使ってやる場合、ここで使われる関数の戻り値/引数の型は

  1. t_value1
  2. 1 つ数が少ないマルチヴァリュー型への参照
  3. void

になります。となると、「1 つ数が少ないマルチヴァリュー型」を typedef してやれば、これらの関数の定義は全く変える必要はないことになります。例えば Type3 であれば、

template <typename t_value1, typename t_value2, typename t_value3>
class Type3
{
private:
    typedef Type2<t_value2, t_value3> multi_t;

    t_value1 m_value1;
    multi_t  m_multi;

public:
    Type3(t_value1& value1, t_value2& value2, t_value3& value3)
        : m_value1(value1), m_multi(value2, value3) { }
    Type3() { }

    const multi_t& operator>>(t_value1& value1) const
        { m_value1 = value1; return m_multi; }
    multi_t& operator>>(t_value1& value1)
        { m_value1 = value1; return m_multi; }

    t_value1 Get() const
        { return m_value1; }

    const multi_t& Skip() const
        { return m_multi; }
    multi_t& Skip()
        { return m_multi; }
};

という風になります。これを見れば分かるとおり、増やした時に変更が必要になるのは赤で書いたところのみです。現実問題そこまで沢山のマルチヴァリュー型が必要になることはないので、この程度の手間で新しいクラスが作れるのであれば全く問題ないように思えます。

 こうしてしまえば型名はクラステンプレートを直接使った Type2<int, int> のような形になり、インテリセンスが有効になります。

 従って結論としては、「新しいクラスを作ったので問題ない!」ということです。


 で、その場合、Value 系関数はどうなるのでしょうか?

 コンストラクタの引数は参照渡しにしてあります。これは上記コンストラクタにある m_multi(value2, value3) の部分でコピーコンストラクタが呼ばれるのを防ぐためです。従って、前回話した通り、リテラルを直接指定する必要がある場合を考えるとやはり Value 系関数があった方が便利だということが分かります。

 しかし、そこですることと言えば、値渡しされてきた値を使ってコンストラクタを呼ぶだけです。従って、例えば Value3 は次の様になります。

template <typename t_value1, typename t_value2, typename t_value3>
Type3 <t_value1, t_value2, t_value2>
    Value3(t_value1 value1, t_value2 value2, t_value3 value3)
{
    return Type3<t_value1, t_value2, t_value3>
        (value1, value2, value3);
}

 非常にすっきりすることが分かります。

 従ってコピーコンストラクタが呼ばれるのは、Value 系関数を呼ぶときと、Type 系クラスのメンバを初期化するときの 2 回のみになります。


 以上のように、2 つしかクラステンプレートを用意しなかった場合よりも随分と良くなっていることが分かります。

 そして、今回はさらに Skip と Get の問題を取り扱いたいと思います。

 Skip と Get のどこに問題があるのかというと、普通のメンバ関数になっているところです。前回出てきた某氏(やねうらお氏)にもコメントをいただいたのですが、Skip を

foo() >> a >> 0 >> b;

のような感じで行いたいわけです。これなら前回の様に (foo() >> a).Skip() >> b; といちいち ( ) をつけなくてもすむわけです。

 これを実現するには ios のマニピュレータが参考になります。即ち、上記では 0 となっているのですが、これを hex や setw のようなもので実現してやろうというわけです。こんな感じですね。

if(foo() >> str >> mvskip >> mvget)
    cout << str << endl;

 実にすっきりすることが分かります。

 では、この mvskip や mvget はどのようなものになるのでしょうか? これは new の nothrow が参考になります。nothrow は new が失敗した時に例外を返さずに NULL を返すようにするためのものです。new(nothrow) という風に使うわけですが、nothrow は次の様に定義されています。

// ↓ struct ではなく class のこともあるかも
struct nothrow_t { };
extern const nothrow_t nothrow;

 つまり、ダミーの型を使ってオーバーロードの呼びわけを行うわけです。実際に nothrow_t という構造体を使うことはないので、実際に使うオーバーロード関数とかぶることはありません。

 従ってやることは、ダミー型を作って、その型を持ったダミーオブジェクトをマニピュレータとして利用する、ということになります。こんな感じですね...。

// スキップ用のダミーオブジェクト
const class CMultivalueSkip { } mvskip;

// 値取得用のダミーオブジェクト
const class CMultivalueGet  { } mvget;

template <typename t_value1, typename t_value2, typename t_value3>
class Type3
{
private:
    typedef Type2<t_value2, t_value3> multi_t;

    t_value1 m_value1;
    multi_t  m_multi;

public:
    Type3(t_value1& value1, t_value2& value2, t_value3& value3)
        : m_value1(value1), m_multi(value2, value3) { }
    Type3() { }

    const multi_t& operator>>(t_value1& value1) const
        { m_value1 = value1; return m_multi; }
    multi_t& operator>>(t_value1& value1)
        { m_value1 = value1; return m_multi; }

    t_value1 operator>>(CMultivalueGet) const
        { return m_value1; }

    const multi_t& operator>>(CMultivalueSkip) const
        { return m_multi; }
    multi_t& operator>>(CMultivalueSkip)
        { return m_multi; }
};

 VC++.NET の nothrow_t では nothrow の実体を別のところに用意しておいて extern してますが、ここでは定数にするだけにしておきます。定数にしておくとファイルスコープ変数になるのでリンクでの二重定義が出ませんが、インクルードしたソースファイルの数だけ実体が作られます。しかし、定数は極力メモリを割り当てずにすむように展開され、しかも今回の場合は inline 展開と最適化によってダミーオブジェクトに関するコードが完全に消えてしまうことが期待できるので、これで問題ありません。

int main()
{
00401080  sub         esp,8
	const char* str;
	if(Season(5) >> str >> mvget)
00401083  lea         eax,[esp]
00401087  push        5
00401089  push        eax
0040108A  call        Season (401000h)
0040108F  mov         cl,byte ptr [eax+4]  ; ← mvget の部分
00401092  add         esp,8
00401095  test        cl,cl
00401097  je          main+29h (4010A9h)
		printf("%s\n", str);
00401099  mov         ecx,dword ptr [eax]
0040109B  push        ecx
0040109C  push        offset string "%s\n" (42114Ch)
004010A1  call        printf (413EB6h)
004010A6  add         esp,8

	return 0;
004010A9  xor         eax,eax
}
004010AB  add         esp,8
004010AE  ret

 これで mvskip と mvget をマニピュレータとすることによりスキップと値の直接取得が便利に行えるようになりました。

 以上をまとめた最新のマルチヴァリューヘッダを ここ (multivalue.h) に置いておきます。自由に使ったってください。


 これでマルチヴァリューは完成...だといいなぁ(苦笑)。現実問題としては Type4 とか Type5 とか数の多いマルチヴァリューを使うよりは、自分でクラスや構造体を作って使った方が可読性・保守性の観点から見て有利だと思います。ので、実際には Type5 とかを使うことはないでしょうし、ましてや multivalue.h に Type7/Value7 以降を追加することはないでしょうね。


目次に戻る

トップページに戻る

Last update was done on 2002.6.10