因式分解技术找到类似的方法吗?

发布于 2024-12-04 05:02:19 字数 3158 浏览 2 评论 0原文

我正在寻找一种技术来分解类似的方法。问题如下。我需要一个容器上的查找方法,不需要修改容器内容来进行搜索。但是,应该有一个 const 和一个非 const 版本,因为在返回迭代器而不是 const_iterator 的情况下,它可能会导致容器的修改。 在这两种情况下,代码将完全相同,只是访问器将被评估为 constXXX 或 XXX,并且编译器将完成这项工作。 Hoewer 从设计和维护的角度来看,将这两种方法实施两次看起来并不明智。 (我真的很想避免使用宏......) 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++ 大师提出了一个好的设计模式来解决该问题。我特别想知道是否有人认为这两种方法之一存在问题(特别是在性能方面)。由于第一个更容易理解,我更喜欢它,尤其是在阅读完之后: C++ 中的常量和编译器优化 这似乎让我不再害怕编写 const_cast 并破坏我的表演。

提前谢谢你,干杯,

曼努埃尔

I am looking for a technique to factor find like methods. The problem is the following. I need a find method on a container that does not need to modify the container contents to do the search. However there should be a const and a non-const version of it since, it could lead to the modification of the container in the case an iterator is returned instead of a const_iterator.
In those two cases, the code will be exactly the same, only the accessors will be evaluated to constXXX or XXX and the compiler will do the job. Hoewer from a design and maintaining point of view it does not look smart to have those two methods implemented two times. (And I would really like to avoid using a macro for that...)
What I mean is also very well illustrated by that piece of code from the gcc implementation of the stl in stl_tree.h:

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

You can see that the prototypes of the methods are different but the code written in the implementation is actually the same.

I came up with two possible solutions:
the first one is with a const_cast and the other one is with a helper templated struct.
I have produced a simple example of those two approaches here:

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

It is probably a very well known problem, and I would like to know if some c++ guru comes up with a good design pattern to fix that problem. And especially I would like to know if someone sees a problem (in particular in terms of performances) with one of those two approaches. As the first one is far more easy to understand I would prefer it, especially after having reading that:
Constants and compiler optimization in C++
that seems to allow me to do not fear to write a const_cast and break my performances.

Thank you in advance, cheers,

Manuel

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

静谧 2024-12-11 05:02:19

在具有相同实现的 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 时,您才可以删除 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 进行转换到迭代器,所以你就完蛋了,除非你使用普通指针。

一种解决方案是尽可能分解出最多的内容,以便可以const_cast,并在最后创建一个迭代器。

The idiomatic way to share code between const and non-const member functions with the same implementation is to const_cast in the non-const one:

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

This works provided the return value of bar is a member object of bar, which is in fact not const when you call the non-const bar (so that const_cast is legal).

You cannot write the non-const version and const_cast in the const one: this is undefined behavior. You are allowed to remove constness only if the object was not const in the first place.

In your example code, since you use bare pointers, you can do:

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

but as soon as you use more complex iterator types, this won't work: indeed, there is no conversion from standard containers' const_iterator to iterator, so you're screwed, unless you use plain pointers.

One solution is to factor out the most you can so that you can const_cast, and manufacture an iterator at the very end.

病毒体 2024-12-11 05:02:19

可能没有很好的解决方案。 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*/ );
  }
};

There might not be very good solutions. Both const overloads and iterator/const_iterator are rather clumsy tools to work with.

In the first case, it might be better to let the const version do the work and the non-const version do the casting. That way the compiler would be able to check if your algorithm indeed doesn't modify the container.

Casting a const_iterator to iterator might be a bit awkward, as it would depend on the implementation details. But you could make a private helper to encapsulate this in a single place.

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

In the second case, you could reduce the verbosity a bit by using template functions and their ability to deduce at least the argument types.

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*/ );
  }
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文