我编写了自己的通用树实现,在为它编写迭代器时,我遇到了const正确性问题。我目前遇到的问题如下:
这是我写的DFS迭代器的头文件:
template<class Item>
class DFSIterator
{
public:
DFSIterator(const Item& rRootNode);
~DFSIterator();
DFSIterator* First();
DFSIterator* operator++(int rhs);
Item* operator*() const;
Item* operator->() const;
bool isDone() const;
template <class Node> friend class Node;
private:
void initListIterator(const Item* currentNode);
bool m_bIsDone;
const Item* m_pRootNode;
const Item* m_pCurrentNode;
ListIterator<Item>* m_pCurrentListIter;
std::map<const Item*, ListIterator<Item>*> m_listMap;
};
所以我关注的是取消引用运算符:
template<class Item>
Item* DFSIterator<Item>::operator*() const
{
if(isDone())
{
return NULL;
}
else
{
return const_cast<Item*>(m_pCurrentNode);
}
}
在那里做一个const_cast是否合适?我想知道如果用户将const对象放入容器中,这是否会导致问题?
答案 0 :(得分:2)
当你的构造函数使用const Item
时,你的运算符应该返回一个const指针。
如果要返回非const项,则应为构造函数使用非const参数。一个解决方案是使用const对象的基类,以及使用非const对象的子类(有点在Objc中完成,例如NSString和NSMutableString)。
答案 1 :(得分:2)
不要抛弃const,因为它会破坏它的含义! STL有一个const_iterator和迭代器类是有原因的。如果你想要const正确,你将不得不实现两个单独的迭代器类。迭代器的目标之一是模仿保存指针。因此,如果超出迭代范围,则不希望返回null,如果实际发生这种情况,则需要引发调试断言。
const迭代器类看起来像这样:
template<class Item>
class DFSIteratorConst
{
public:
DFSIteratorConst(const Item& node)
{
m_pCurrentNode = &node;
};
const Item& operator*() const
{
assert(!IsDone());
return *m_pCurrentNode;
}
void operator++()
{
assert(!IsDone());
m_pCurrentNode = m_pCurrentNode->next;
}
operator bool() const
{
return !IsDone();
}
bool IsDone() const
{
return m_pCurrentNode == nullptr;
}
private:
Item const * m_pCurrentNode;
};
有几点需要注意:
while(it) { it++; }
而不是while(!it.IsDone()) { it++; }
,类似于经典指针。从我使用std::map
可以看出,你已经在使用STL了。也许使用现有的STL迭代器会更容易吗?
答案 2 :(得分:1)
const_cast
本身始终是安全的,但任何写入已声明为const_cast
的{{1}} ed值的尝试都是未定义的行为。
在这种情况下,如果返回的指针指向一个声明为const
的值并且您尝试修改它,则会得到未定义的行为。
经验法则:如果您需要使用const
除了与const错误代码进行互操作之外的任何事情,那么您的设计就会被破坏。
标准在 7.1.6.1 cv-qualifiers :
中说明除了可以修改声明为mutable(7.1.1)的任何类成员之外,任何在其生命周期内修改const对象的尝试(3.8)都会导致未定义的行为。
答案 3 :(得分:1)
通常,您编写两个迭代器版本,iterator
和const_iterator
。
有一些模板技巧可以避免Boost.Iterator库文档中公开的代码重复。了解Iterator Adaptor是如何定义的,因为它很长。
问题是你会写一个有BaseIterator
意识的const
,然后提供别名:
typedef BaseIterator<Item> Iterator;
typedef BaseIterator<Item const> ConstIterator;
诀窍在于如何定义转换构造函数,以便Iterator
到ConstIterator
是可能的,但反之则不然。
答案 4 :(得分:1)
真正的问题是这个代码/接口是“撒谎”。
构造函数说:我不会改变你(根)。 但迭代器提供了可更改的项目。
要“诚实”,构造函数需要一个可更改的根,即使它在此类中不会更改, 或者这个类不会给出可更改的项目。
然而,Item本身定义了它是否会让可变的孩子离开。 根据该代码可能无法编译。也许这就是你建造的原因。
长谈,短暂的意思:这种设计很差,应该改变
可能你需要两个模板,具体取决于给定孩子的“常数”