C++では、ときどき奇怪な振る舞いに振り回されることがあります。
あるコンテナ クラスUniqueContainerを作りました。このクラスは内部にコンテナを保持しますが、そのコンテナに要素を保存するときに特別な操作をします。具体的には、各要素を必ず一意とし、重複する要素を挿入 (insert) する操作は無視します (そのほか、優先順位付きキューとして機能する、キーで値を検索できるなどの機能を実装しています)。しかし、標準ライブラリ (STL) でサポートされている一般的なシーケンス操作 (UniqueContainerの要素で他のコンテナを初期化したりUniqueContainerの要素を検索したり) は幅広く使えるようにしたいので、コンテナに対するconst_iteratorは取得できるようにします。ただし、今後個別の事例に対応して拡張できるように、派生クラスにはコンテナの要素を直接変更する操作を許可します (派生クラスでコンテナの秩序を壊さないように注意を払うのは、派生クラスを作成するプログラマの責任とします)。
コンテナucには、動的に確保したオブジェクトを指すポインタを格納します。当然、使い終わったら領域を解放する必要があります。以前に作ったポインタ削除用の述語があるので、それを流用します。
ところが、このコンパイルが通らない。delptr() からiterator UniqueContainer::begin( void) にはアクセスできないというエラーが発生して、はねられてしまいます。
for_eachとDeletePointerを展開した後は
になるはずで、コンテナucの要素自体は変更しませんから、delptr()からアクセス可能なconst_iterator UniqueContainer::begin( void) constのバージョンを呼び出せば構わないはずです。ところが、コンパイラは非constバージョンのbegin()とend()を選択します。非constバージョンはprotectedメンバですから、当然delptr()から呼び出すことはできません。
と変更すればconstバージョンのbegin()とend()が呼び出されます。どうやら、delptr()がクラスHogeのオブジェクトを変更できるかできないか (実際に変更するかどうかではなく、宣言上変更できるかどうか) によってconst_iteratorかiteratorかが選択されているようです。
仕方がないので、非constバージョンのbegin()とend()の名前を変えることにしました。
標準ライブラリ (STL) のコンテナでは、const_iteratorバージョンとiteratorバージョンのいずれでも同じ名前begin()とend()を使っています。当然UniqueContainerでも両方とも同じ名前にして統一したいのですが、仕方ありません。名前を別にしておけば、void delptr( void) constでなく元のvoid delptr( void)のままでもちゃんとconst_iteratorバージョンが選択され、コンパイル エラーは出ません (じゃ、最初からそうしてよ)。
名前が違っていれば、派生クラスを書くときに、基底クラスでコンテナに課している制約 (各要素が一意であるなど) を強く意識することになりますから、これはこれでいいかな。
あるコンテナ クラス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バージョンが選択され、コンパイル エラーは出ません (じゃ、最初からそうしてよ)。
名前が違っていれば、派生クラスを書くときに、基底クラスでコンテナに課している制約 (各要素が一意であるなど) を強く意識することになりますから、これはこれでいいかな。