因式分解技术找到类似的方法吗?
我正在寻找一种技术来分解类似的方法。问题如下。我需要一个容器上的查找方法,不需要修改容器内容来进行搜索。但是,应该有一个 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
在具有相同实现的 const 和非常量成员函数之间共享代码的惯用方法是在非常量成员函数中进行
const_cast
:只要返回值为
bar
,此方法就可以工作。是bar
的成员对象,当您调用非 constbar
时,它实际上不是 const(因此const_cast
是合法的)。您不能在 const 版本中编写非常量版本和
const_cast
:这是未定义的行为。仅当对象一开始就不是 const 时,您才可以删除 constness。在您的示例代码中,由于您使用裸指针,您可以这样做:
但是一旦您使用更复杂的迭代器类型,这将不起作用:实际上,没有从标准容器的
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:This works provided the return value of
bar
is a member object ofbar
, which is in fact not const when you call the non-constbar
(so thatconst_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:
but as soon as you use more complex iterator types, this won't work: indeed, there is no conversion from standard containers'
const_iterator
toiterator
, 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.可能没有很好的解决方案。 const 重载和 iterator/const_iterator 都是相当笨拙的工具。
在第一种情况下,最好让 const 版本完成工作,让非 const 版本完成转换。这样编译器就能够检查您的算法是否确实没有修改容器。
将
const_iterator
转换为iterator
可能有点尴尬,因为这取决于实现细节。但是您可以创建一个私人助手将其封装在一个地方。在第二种情况下,您可以通过使用模板函数及其至少推断参数类型的能力来稍微减少冗长性。
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
toiterator
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.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.