2

私は、メソッドのようなものを因数分解するためのテクニックを探しています。問題は次のとおりです。検索を行うためにコンテナの内容を変更する必要のないコンテナのfindメソッドが必要です。ただし、const_iteratorの代わりにイテレータが返された場合にコンテナが変更される可能性があるため、constバージョンとnon-constバージョンが存在する必要があります。これらの2つのケースでは、コードはまったく同じであり、アクセサーのみがconstXXXまたはXXXに評価され、コンパイラーがその役割を果たします。設計と保守の観点から見ると、これら2つの方法を2回実装するのは賢明ではありません。(そして、そのためにマクロを使用することは本当に避けたいです...)私が意味することは、stl_tree.hのstlのgcc実装からのそのコードによっても非常によく示されています。

template<typename _Key, typename _Val, typename _KeyOfValue, 
  typename _Compare, typename _Alloc>
  typename _Rb_tree<_Key, _Val, _KeyOfValue,
          _Compare, _Alloc>::iterator
  _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
find(const _Key& __k)
{
  iterator __j = _M_lower_bound(_M_begin(), _M_end(), __k);
  return (__j == end()
      || _M_impl._M_key_compare(__k,
                _S_key(__j._M_node))) ? end() : __j;
}

template<typename _Key, typename _Val, typename _KeyOfValue,
       typename _Compare, typename _Alloc>
typename _Rb_tree<_Key, _Val, _KeyOfValue,
          _Compare, _Alloc>::const_iterator
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
find(const _Key& __k) const
{
  const_iterator __j = _M_lower_bound(_M_begin(), _M_end(), __k);
  return (__j == end()
      || _M_impl._M_key_compare(__k, 
                _S_key(__j._M_node))) ? end() : __j;
}

メソッドのプロトタイプは異なりますが、実装で記述されたコードは実際には同じであることがわかります。

私は2つの可能な解決策を思いつきました。1つはconst_castを使用する方法で、もう1つはヘルパーテンプレート構造体を使用する方法です。ここでは、これら2つのアプローチの簡単な例を示しました。

#include <iostream>
using namespace std;

struct Data
{
  typedef int*       iterator;
  typedef const int* const_iterator;

  int m;

  Data():m(-3){}
};

struct A : public Data
{
  const_iterator find(/*const Key& k */) const
  {
    A *me = const_cast < A* > ( this );
        return const_iterator( me->find(/*k*/) );
  }

  iterator find(/*const Key& k */){
    return &m; }
};

//the second one is with the use of an internal template structure:

struct B : public Data
{

  template<class Tobj, class Titerator>
    struct Internal
   {
      Titerator find( Tobj& obj/*, const Key& k */ ){
        return &(obj.m); }
    };

  const_iterator find( /*const Key& k */ ) const
  {
    Internal<const B, const_iterator> internal;
    return internal.find( *this/*, k*/ );
  }

  iterator find( /*const Key& k */ )
  {
    Internal<B,iterator> internal;
    return internal.find( *this/*, obs*/ );
  }
};


int main()
{
  {
    A a;
    a.find();
    A::iterator it = a.find();
    cout << *it << endl;


    const A& a1(a);
    A::const_iterator cit = a1.find();
    cout << *cit << endl;
  }

  {
    B b;
    b.find();
    B::iterator it = b.find();
    cout << *it << endl;


    const B& b1(b);
    B::const_iterator cit = b1.find();
    cout << *cit << endl;
  }
}

これはおそらく非常によく知られている問題であり、C++の第一人者がその問題を修正するための優れたデザインパターンを考え出すかどうかを知りたいと思います。特に、これら2つのアプローチのいずれかで(特にパフォーマンスの観点から)問題が発生したかどうかを知りたいと思います。最初のものははるかに理解しやすいので、特にそれを読んだ後は、それを好むでしょう: 定数とC ++ でのコンパイラの最適化により、const_castを記述してパフォーマンスを損なうことを恐れないようです。

よろしくお願いします、乾杯、

マヌエル

4

2 に答える 2

1

同じ実装でconstメンバー関数とnon-constメンバー関数の間でコードを共有する慣用的な方法は、non-constメンバー関数を使用することですconst_cast

struct foo
{
    const int* bar() const;
    int* bar() 
    {
        const int* p = static_cast<const foo*>(this)->bar();

        // Perfectly defined since p is not really
        // const in the first place
        return const_cast<int*>(p);
    }
};

これは、の戻り値barがのメンバーオブジェクトである場合に機能barします。これは、non-constを呼び出すときに実際にはconstではありませんbar(したがって、これconst_castは正当です)。

非constバージョンとconstバージョンを書き込むことはできませんconst_cast。これは未定義の動作です。オブジェクトがそもそもconstでなかった場合にのみ、 constnessを削除できます。

サンプルコードでは、ベアポインターを使用しているため、次のことができます。

struct A : public Data
{
  const_iterator find(const Key& k) const
  {
      // The real implementation of find is here
  }

  iterator find(const Key& k)
  {
      // Not the other way around !
      const_iterator p = static_cast<const A*>(this)->find(k);
      return const_cast<iterator>(p);
  }
};

しかし、より複雑なイテレータタイプを使用するとすぐに、これは機能しません。実際、標準のコンテナからへの変換はないconst_iteratorためiterator、プレーンポインタを使用しない限り、失敗します。

1つの解決策は、できる限り多くのことを考慮して、const_cast最後にイテレータを製造することです。

于 2011-09-11T10:03:02.837 に答える
0

あまり良い解決策はないかもしれません。どちらもconstiterator/const_iteratorがオーバーロードし、操作するのにかなり不器用なツールです。

最初のケースでは、constバージョンに作業を任せ、non-constバージョンにキャストを任せたほうがよい場合があります。そうすれば、コンパイラは、アルゴリズムが実際にコンテナを変更していないかどうかを確認できます。

const_iteratortoのキャストはiterator、実装の詳細に依存するため、少し厄介かもしれません。ただし、これを1か所にカプセル化するプライベートヘルパーを作成することもできます。

struct A : public Data
{
  iterator find(/*const Key& k */)
  {
    const A *me = this;
    return remove_const_from( me->find(/*k*/) );
  }

  const_iterator find(/*const Key& k */) const{
    return &m; }

    private:
        //could be also static, but in the general case, *this might be needed
        iterator remove_const_from(const_iterator p)
        {
            //in this case just a const_cast
            return const_cast<int*>(p);
        }
};

2番目のケースでは、テンプレート関数と、少なくとも引数の型を推測する機能を使用して、冗長性を少し減らすことができます。

struct B : public Data
{
    struct Internal //eventually, could be just a free function?
   {
      template<class Titerator, class Tobj>
      static Titerator find( Tobj& obj/*, const Key& k */ ){
        return &(obj.m); }
    };

  const_iterator find( /*const Key& k */ ) const
  {
    return Internal::find<const_iterator>( *this/*, k*/ );
  }

  iterator find( /*const Key& k */ )
  {
    return Internal::find<iterator>( *this/*, obs*/ );
  }
};
于 2011-09-11T09:21:49.817 に答える