在这里做const演员是否安全?

时间:2012-03-23 15:10:12

标签: c++ iterator const-cast

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

5 个答案:

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

有几点需要注意:

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

从我使用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)

通常,您编写两个迭代器版本,iteratorconst_iterator

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

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

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

诀窍在于如何定义转换构造函数,以便IteratorConstIterator是可能的,但反之则不然。

答案 4 :(得分:1)

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

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

要“诚实”,构造函数需要一个可更改的根,即使它在此类中不会更改, 或者这个类不会给出可更改的项目。

然而,Item本身定义了它是否会让可变的孩子离开。 根据该代码可能无法编译。也许这就是你建造的原因。

长谈,短暂的意思:这种设计很差,应该改变

可能你需要两个模板,具体取决于给定孩子的“常数”