我有一个STL容器,其元素类型为const std::shared_ptr<MyClass>
。
我想为用户提供两种迭代器类型:
MyContainer::iterator
typedefed为std::vector<const std::shared_ptr<MyClass>>::iterator
(应与std::vector<const std::shared_ptr<const MyClass>>::const_iterator
MyContainer::const_iterator
typedefed为std::vector<const std::shared_ptr<const MyClass>>::iterator
(应与std::vector<const std::shared_ptr<const MyClass>>::const_iterator
换句话说,我希望“const
”引用MyClass
constness,而不是shared_ptr
constness。我找到第二个迭代器类型的解决方案是获取第一个,这很容易(例如使用vector::begin
),然后使用static_cast
将其转换为第二个类型(fixme:无需使用) const_cast
因为我正在添加constness,而不是删除它。)
这是实现这一目标的常见的良好设计方式,还是有更好/更常见的方式?
答案 0 :(得分:2)
typedefed为
std::vector<const std::shared_ptr<MyClass>>::iterator
(与std::vector<std::shared_ptr<const MyClass>>::const_iterator
的类型相同
但它可能不是同一类型。迭代器不仅仅是指针。如果在iterator
中定义const_iterator
和vector
类型,则它们是完全不相关的类型:
template<typename T>
class vector
{
class iterator;
class const_iterator;
// ...
vector<const int>
与vector<int>
的类型不同,因此它们的嵌套类型也不同。就编译器而言,它们是完全不相关的类型,即您不能只将const
移动到此类型中的任何点并获得兼容类型:
vector<const shared_ptr<const T>>::iterator
您无法使用const_cast
在不相关的类型之间进行转换。您可以使用static_cast
将vector<T>::iterator
转换为vector<T>::const_iterator
,但它不是真正的演员,您可以从前者构建后者,这是允许的,因为转换是由标准。
您可以使用shared_ptr<const T>
将shared_ptr<T>
转换为const_pointer_cast<T>
但又仅因为它被定义为按标准工作,而不是因为这些类型本质上是兼容的而不是因为它“只是工作“像普通的OL'指针。
由于vector
的迭代器没有提供你想要的深度常量,你需要自己编写,但这并不难:
class MyClass { };
class MyContainer
{
typedef std::vector<std::shared_ptr<MyClass>> container_type;
container_type m_cont;
public:
typedef container_type::iterator iterator;
class const_iterator
{
typedef container_type::const_iterator internal_iterator;
typedef std::iterator_traits<internal_iterator> internal_traits;
const_iterator(internal_iterator i) : m_internal(i) { }
friend class MyContainer;
public:
const_iterator() { }
const_iterator(iterator i) : m_internal(i) { }
typedef std::shared_ptr<const MyClass> value_type;
typedef const value_type& reference;
typedef const value_type* pointer;
typedef internal_traits::difference_type difference_type;
typedef internal_traits::iterator_category iterator_category;
const_iterator& operator++() { ++m_internal; return *this; }
const_iterator operator++(int) { const_iterator tmp = *this; ++m_internal; return tmp; }
reference operator*() const { m_value = *m_internal; return m_value; }
pointer operator->() const { m_value = *m_internal; return &m_value; }
// ...
private:
internal_iterator m_internal;
mutable value_type m_value;
};
iterator begin() { return m_cont.begin(); }
const_iterator begin() const { return const_iterator(m_cont.begin()); }
// ...
};
迭代器类型正在处理一些事情(operator--
,operator+
),但它们很容易添加,遵循与已经显示的相同的想法。
要注意的关键点是,为了使const_iterator::operator*
返回引用,需要将shared_ptr<const MyClass>
对象存储为迭代器的成员。该成员充当shared_ptr<const MyClass>
值的“缓存”,因为底层容器的真实元素是不同的类型shared_ptr<MyClass>
,因此您需要某处缓存转换后的值,以便对其进行引用回。注:这样做会略微破坏迭代器的要求,因为以下内容无法按预期工作:
MyContainer::const_iterator ci = c.begin();
const shared_ptr<const MyClass>& ref = *ci;
const MyClass* ptr = ref.get();
++ci;
(void) *ci;
assert( ptr == ref.get() ); // FAIL!
断言失败的原因是*ci
不返回对容器的底层元素的引用,而是返回迭代器的成员,该成员通过以下增量和取消引用进行修改。如果这种行为不可接受,则需要从迭代器返回代理而不是缓存值。或者在shared_ptr<const MyClass>
被取消引用后返回const_iterator
。 (让这100%正确的困难是STL容器不试图模拟深度常数的原因之一!)
定义自己的迭代器类型的许多工作都由boost::iterator_adaptor
实用程序完成,因此上面的示例仅对于说明非常有用。使用该适配器,您只需要执行此操作即可获得具有所需行为的自定义迭代器类型:
struct iterator
: boost::iterator_adaptor<iterator, container_type::iterator>
{
iterator() { }
iterator(container_type::iterator i) : iterator_adaptor(i) { }
};
struct const_iterator
: boost::iterator_adaptor<const_iterator, container_type::const_iterator, std::shared_ptr<const MyClass>, boost::use_default, std::shared_ptr<const MyClass>>
{
const_iterator() { }
const_iterator(iterator i) : iterator_adaptor(i.base()) { }
const_iterator(container_type::const_iterator i) : iterator_adaptor(i) { }
};
答案 1 :(得分:2)
boost::iterator_adaptor
可以很容易地根据另一个迭代器类型定义自己的迭代器类型。因此,您可以根据需要进行设置,以使*iter
为const shared_ptr<MyClass>&
或const shared_ptr<const MyClass>&
。
虽然在const_iterator
案例中,如果您实际拥有的内容为const shared_ptr<const MyClass>&
,则解除引用无法返回shared_ptr<MyClass>
。因此,我们将const_iterator::reference
定义为shared_ptr<const MyClass>
并按值返回。
#include <boost/iterator/iterator_adaptor.hpp>
class MyContainer {
public:
class iterator;
class const_iterator;
class iterator :
public boost::iterator_adaptor<
iterator, // This class, for CRTP
std::vector<const std::shared_ptr<MyClass>>::const_iterator,
// Base type
const std::shared_ptr<MyClass> > // value_type
{
public:
iterator() {}
iterator(const iterator&) = default;
private:
friend class MyContainer; // allow private constructor
friend class boost::iterator_core_access; // allow dereference()
explicit iterator(base_type iter) : iterator_adaptor(iter) {}
const std::shared_ptr<MyClass>& dereference() const
{ return *base_reference(); }
};
class const_iterator :
public boost::iterator_adaptor<
const_iterator, // This class, for CRTP
std::vector<const std::shared_ptr<MyClass>>::const_iterator,
// Base type
const std::shared_ptr<const MyClass>, // value_type
boost::use_default, // difference_type
std::shared_ptr<const MyClass> > // reference_type
{
public:
const_iterator();
const_iterator(const const_iterator&) = default;
// Implicit conversion from iterator to const_iterator:
const_iterator(const iterator& iter) : iterator_adaptor(iter.base()) {}
private:
friend class MyContainer; // allow private constructor
friend class boost::iterator_core_access; // allow dereference()
explicit const_iterator(base_type iter) : iterator_adaptor(iter) {}
std::shared_ptr<const MyClass> dereference() const
{ return *base_reference(); }
};
iterator begin() { return iterator(mVec.begin()); }
iterator end() { return iterator(mVec.end()); }
const_iterator begin() const { return cbegin(); }
const_iterator end() const { return cend(); }
const_iterator cbegin() const { return const_iterator(mVec.begin()); }
const_iterator cend() const { return const_iterator(mVec.end()); }
private:
std::vector<const std::shared_ptr<MyClass>> mVec;
};
答案 2 :(得分:1)
shared_ptr和其他标准智能指针的设计并未考虑深度常量。他们试图尽可能接近原始指针的使用,而原始指针的常量不会影响指针的常量。
Andrei Alexandrescu的Loki::SmartPtr(在他的 Modern C ++ Design 中描述)将引用计数和深度常量实现为策略,这将为您提供所需的效果。如果您不介意切换到非标准智能指针以获得非标准行为,那么这可能是一种方法。