みぃちゃんの頭の中はおもちゃ箱

略してみちゃばこ。泣いたり笑ったり

C++の怪

2014年09月16日 22時31分37秒 | IT・デジタル
C++では、ときどき奇怪な振る舞いに振り回されることがあります。

あるコンテナ クラスUniqueContainerを作りました。このクラスは内部にコンテナを保持しますが、そのコンテナに要素を保存するときに特別な操作をします。具体的には、各要素を必ず一意とし、重複する要素を挿入 (insert) する操作は無視します (そのほか、優先順位付きキューとして機能する、キーで値を検索できるなどの機能を実装しています)。しかし、標準ライブラリ (STL) でサポートされている一般的なシーケンス操作 (UniqueContainerの要素で他のコンテナを初期化したりUniqueContainerの要素を検索したり) は幅広く使えるようにしたいので、コンテナに対するconst_iteratorは取得できるようにします。ただし、今後個別の事例に対応して拡張できるように、派生クラスにはコンテナの要素を直接変更する操作を許可します (派生クラスでコンテナの秩序を壊さないように注意を払うのは、派生クラスを作成するプログラマの責任とします)。
template< class T>
class UniqueContainer
{
public:
  bool insert( void);
  ...
  const_iterator begin( void) const { return c_.begin(); }
  const_iterator end  ( void) const { return c_.end  (); }

protected:
  iterator begin( void) { return c_.begin(); }
  iterator end  ( void) { return c_.end  (); }

private:
  SomeContainer< T> c_;
};


このクラスを利用するクラスを作りました。
class Hoge
{
UniqueContainer< SomeType *> uc;

void delptr( void);
...
};

コンテナucには、動的に確保したオブジェクトを指すポインタを格納します。当然、使い終わったら領域を解放する必要があります。以前に作ったポインタ削除用の述語があるので、それを流用します。

template< class T>
struct DeletePointer : public std::unary_function< T *, T *>
{
T *operator()( T *p) const { delete p; return 0; }
};

void delptr( void)
{
for_each( uc.begin(), uc.end(), DeletePointer< T>());
}

ところが、このコンパイルが通らない。delptr() からiterator UniqueContainer::begin( void) にはアクセスできないというエラーが発生して、はねられてしまいます。
for_eachとDeletePointerを展開した後は

for (first != last)
delete *first++;

になるはずで、コンテナucの要素自体は変更しませんから、delptr()からアクセス可能なconst_iterator UniqueContainer::begin( void) constのバージョンを呼び出せば構わないはずです。ところが、コンパイラは非constバージョンのbegin()とend()を選択します。非constバージョンはprotectedメンバですから、当然delptr()から呼び出すことはできません。

void delptr( void) const
{
for_each( uc.begin(), uc.end(), DeletePointer< T>());
}

と変更すればconstバージョンのbegin()とend()が呼び出されます。どうやら、delptr()がクラスHogeのオブジェクトを変更できるかできないか (実際に変更するかどうかではなく、宣言上変更できるかどうか) によってconst_iteratorかiteratorかが選択されているようです。

仕方がないので、非constバージョンのbegin()とend()の名前を変えることにしました。

template< class T>
class UniqueContainer
{
...
protected:
iterator begin_nc( void) { return c_.begin(); }
iterator end_nc ( void) { return c_.end (); }
...
};

標準ライブラリ (STL) のコンテナでは、const_iteratorバージョンとiteratorバージョンのいずれでも同じ名前begin()とend()を使っています。当然UniqueContainerでも両方とも同じ名前にして統一したいのですが、仕方ありません。名前を別にしておけば、void delptr( void) constでなく元のvoid delptr( void)のままでもちゃんとconst_iteratorバージョンが選択され、コンパイル エラーは出ません (じゃ、最初からそうしてよ)。

名前が違っていれば、派生クラスを書くときに、基底クラスでコンテナに課している制約 (各要素が一意であるなど) を強く意識することになりますから、これはこれでいいかな。

最新の画像もっと見る

コメントを投稿

ブログ作成者から承認されるまでコメントは反映されません。