【问题标题】:Technique for factoring find like methods?分解技术找到类似的方法?
【发布时间】:2011-11-14 09:51:41
【问题描述】:

我正在寻找一种技术来寻找类似的方法。问题如下。我需要一个容器上的 find 方法,该方法不需要修改容器内容来进行搜索。但是,它应该有一个 const 和一个非常量版本,因为在返回迭代器而不是 const_iterator 的情况下,它可能导致容器的修改。 在这两种情况下,代码将完全相同,只有访问器将被评估为 constXXX 或 XXX 并且编译器将完成这项工作。从设计和维护的角度来看,将这两种方法实现两次并不明智。 (而且我真的很想避免为此使用宏......) 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;
}

可以看到方法的原型不同,但实现中写的代码其实是一样的。

我想出了两种可能的解决方案: 第一个使用 const_cast,另一个使用辅助模板结构。 我在这里制作了这两种方法的一个简单示例:

#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++ 大师提出了一个好的设计模式来解决这个问题。特别是我想知道是否有人认为这两种方法中的一种存在问题(特别是在性能方面)。因为第一个更容易理解,所以我更喜欢它,尤其是在阅读完之后: Constants and compiler optimization in C++ 这似乎让我不用害怕写一个 const_cast 并破坏我的表现。

提前谢谢你,干杯,

曼努埃尔

【问题讨论】:

  • 没什么私人的,但是我放弃了看Q,代码太伤我的眼睛了。 :(
  • std::find 有什么问题?接受并返回一个非常量迭代器。
  • std::find 只是迭代(线性复杂性),在许多情况下,特别是在上面强调的地图情况下,您可以做得更好。对于这些情况,您需要自己的实现,这就是我需要一个好的设计的事情。当然,问题不限于查找函数,而是任何不修改容器但返回迭代器的函数,您可能希望它在处理 const 对象和迭代器时返回 const_iterators。
  • 愚蠢的问题:你确定std::find 并没有真正检测到它何时被map 调用并使用成员函数吗?我不确定这是否应该是这种情况,但有可能......如果不是,我可能会制作一些特征类来检测某物是否是容器并具有成员查找。
  • 其实我也不确定。但这只是与问题略微相关,因为即使是这种情况,它也会使用容器的 find 方法,然后出现问题:如何编写这些方法而不复制 const 和非 const 上下文的代码。

标签: c++ design-patterns iterator constants const-iterator


【解决方案1】:

在具有相同实现的 const 和非 const 成员函数之间共享代码的惯用方式是在非 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 的成员对象,则此方法有效,当您调用非const bar 时,它实际上不是const(因此const_cast 是合法的)。

您不能在 const 版本中写入非常量版本和 const_cast:这是未定义的行为。 只有在对象一开始不是 const 时,您才可以删除 const。

在您的示例代码中,由于您使用裸指针,您可以这样做:

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_iteratoriterator 的转换,所以你搞砸了,除非你使用普通指针。

一种解决方案是尽可能多地分解,以便您可以const_cast,并在最后制造一个迭代器。

【讨论】:

    【解决方案2】:

    可能没有很好的解决方案。 const 重载和iterator/const_iterator 都是相当笨拙的工具。

    在第一种情况下,让 const 版本完成工作,让非 const 版本进行转换可能会更好。这样编译器就可以检查你的算法是否确实没有修改容器。

    const_iterator 转换为iterator 可能有点尴尬,因为这取决于实现细节。但是您可以创建一个私人助手将其封装在一个地方。

    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);
            }
    };
    

    在第二种情况下,您可以通过使用模板函数及其至少推断参数类型的能力来减少冗长。

    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*/ );
      }
    };
    

    【讨论】:

      猜你喜欢
      • 2014-11-23
      • 1970-01-01
      • 2010-09-19
      • 2017-07-27
      • 1970-01-01
      • 1970-01-01
      • 2013-05-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多