【问题标题】:Is it safe to do a const cast here?在这里做一个 const 演员是否安全?
【发布时间】:2012-03-23 15:10:12
【问题描述】:

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

【问题讨论】:

  • 该运算符重载,实质上使item-&gt;operator*() 等同于const_cast&lt;Item*&gt;(item)。 (至少,只要!isDone()。)那么为什么不首先使用const_cast
  • 这是一个很不寻常的operator*...

标签: c++ iterator const-cast


【解决方案1】:

由于你的构造函数使用const Item,你的操作符应该返回一个常量指针。

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

【讨论】:

  • 但是对模板对象使用继承不是不可能吗?
【解决方案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 迭代器会更容易?

【讨论】:

  • 这里给出了一些优点。谢谢。我真的很喜欢隐式布尔转换!我需要这个项目的通用树实现,而 STL 不提供。我不能使用 boost,因为我们的团队希望尽可能避免外部依赖。
  • 好吧,我认为 Boost 是语言的一部分,因为无论如何标准库是如此有限。我理解人们不想依赖任何需要链接的 boost 库,但只有标头的库很容易设置,而且大多无忧。
【解决方案3】:

const_casting 本身始终是安全的,但任何尝试写入已声明为 constconst_casted 值都是未定义的行为。

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


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


标准在 7.1.6.1 中说 cv-qualifiers

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

【讨论】:

  • 我不同意,这将是未定义的行为如果指向Item 已被定义为const
  • const DFSIterator 的情况下,您不能在 const 对象上调用非常量方法。因此,您不能在 const DFSIterator 上致电运营商
  • 我也是。我不同意第一句话的后半部分。写入 Item 可能会破坏 DFSIterator 中的逻辑,但它完美地定义了 Item 中发生的事情
  • 我不同意经验法则。 const_cast 在提供 const 和非 const 版本的函数时,可以安全地显着减少许多模板中的代码大小。
  • @edA-qamort-ora-y:我猜这个技巧只能从非常量变为常量?即Foo foo; ...; method (const_cast&lt;Foo const&amp;&gt;(foo))?
【解决方案4】:

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

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

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

  • typedef BaseIterator&lt;Item&gt; Iterator;
  • typedef BaseIterator&lt;Item const&gt; ConstIterator;

技巧在于您如何定义转换构造函数,以便将Iterator 转换为ConstIterator 是可能的,但反之则不行。

【讨论】:

    【解决方案5】:

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

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

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

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

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

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

    【讨论】:

    • 我的想法是迭代器本身不会改变树,但会让用户使用 operator*() 访问对当前指向节点的可变引用。我想这不是 const 正确的。
    猜你喜欢
    • 1970-01-01
    • 2015-11-02
    • 2012-05-04
    • 1970-01-01
    • 2021-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多