2

我已经编写了自己的通用树实现,并且在为它编写迭代器时,我遇到了 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 对象放入容器中,这是否会导致问题?

4

5 回答 5

2

当您的构造函数使用 aconst Item时,您的运算符应该返回一个 const 指针。

如果要返回非常量项,则应为构造函数使用非常量参数。一个解决方案是拥有一个使用 const 对象的基类,以及一个使用非 const 对象的子类(在某种程度上它在 Objc 中完成,例如 NSString 和 NSMutableString)。

于 2012-03-23T15:44:16.063 回答
2

不要丢弃 const,因为它会破坏它的含义!STL 有一个 const_iterator 和 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;
};

需要注意的几点:

  • 考虑返回对节点的引用(常量引用)。如果迭代器超过最后一个元素,这将导致您无法返回 null。取消引用过去的结束迭代器通常是不好的行为,您希望在调试构建和测试期间找到这些问题
  • const 迭代器类有一个指向 const 元素的指针。这意味着它可以更改指针(否则您将无法迭代)但不能修改元素本身
  • 如果您想在 while 循环中检查迭代器,则隐式转换为 bool 是实用的。这允许您编写:while(it) { it++; }而不是while(!it.IsDone()) { it++; }再次,类似于经典指针的东西。
  • 如果可用,请使用nullptr

从您的使用中我可以看出,您std::map已经在使用 STL。也许使用现有的 STL 迭代器会更容易?

于 2012-03-23T16:13:34.587 回答
1

const_casting 本身总是安全的,但是任何尝试写入const_cast已声明的 ed 值const都是未定义的行为。

在这种情况下,如果您返回的指针指向一个已声明的值const并且您尝试修改它,您将获得未定义的行为。


经验法则:如果除了与 const 不正确的代码进行互操作之外,您还需要使用其他任何东西,那么您的设计就会被破坏。const_cast


该标准在7.1.6.1 中说 cv-qualifiers

除了可以修改任何声明为 mutable (7.1.1) 的类成员外,任何在 const 对象的生命周期 (3.8) 期间修改它的尝试都会导致未定义的行为。

于 2012-03-23T15:37:19.573 回答
1

通常,您编写两个迭代器版本, aniterator和 a const_iterator

有一些模板技巧可以避免 Boost.Iterator 库文档中公开的代码重复。看看Iterator Adapter是如何定义的,因为它很长。

问题是你会写一个BaseIteratorconst意识的,然后提供别名:

  • typedef BaseIterator<Item> Iterator;
  • typedef BaseIterator<Item const> ConstIterator;

技巧在于您如何定义转换构造函数,Iterator以便ConstIterator可以实现,但反之则不然。

于 2012-03-23T15:48:55.293 回答
1

真正的问题是这个代码/界面是“撒谎”。

构造函数说:我不会改变你(根)。但是迭代器放弃了可变的项目。

为了“诚实”,要么构造函数采用可更改的根,即使它不会在此类中更改,或者此类不提供可更改的 Item 。

但是,Item 本身定义了它是否放弃可变的孩子。根据该代码可能无法编译。也许这就是你建设的原因。

长话短说:这个设计很差,应该改

可能您需要两个模板,具体取决于给定孩子的“const”-ness

于 2012-03-23T16:04:27.147 回答