C ++中的const-correctness语义

时间:2013-10-22 20:18:19

标签: c++ c++11 iterator

for fun and profit™,我正在用C ++编写一个trie类(使用C ++ 11标准。)

我的trie<T>有一个迭代器trie<T>::iterator。 (它们实际上都是功能 const_iterator,因为你无法修改trie的value_type。)迭代器的类声明看起来像这样:

template<typename T>
class trie<T>::iterator : public std::iterator<std::bidirectional_iterator_tag, T> {
    friend class trie<T>;
    struct state {
        state(const trie<T>* const node, const typename std::vector<std::pair<typename T::value_type, std::unique_ptr<trie<T>>>>::const_iterator& node_map_it ) :
            node{node}, node_map_it{node_map_it} {}
// This pointer is to const data:
        const trie<T>* node;
        typename std::vector<std::pair<typename T::value_type, std::unique_ptr<trie<T>>>>::const_iterator node_map_it;
    };
public:
    typedef const T value_type;
    iterator() =default;
    iterator(const trie<T>* node) {
        parents.emplace(node, node->children.cbegin());
            // ...
    }
    // ...
private:
    std::stack<state> parents;
    // ...
};

请注意,node指针已声明为const。这是因为(在我看来)迭代器不应该修改它指向的节点;它只是一个迭代器。

现在,在我的主trie<T>类的其他地方,我有一个具有共同STL签名的擦除函数 - 要擦除数据需要iterator(并返回iterator到下一个对象)。

template<typename T>
typename trie<T>::iterator trie<T>::erase(const_iterator it)
{
    // ...

    // Cannot modify a const object!
    it.parents.top().node->is_leaf = false;

    // ...
}

编译器抱怨,因为node指针是只读的! erase函数肯定应该修改迭代器指向的trie,即使迭代器不应该这样。

所以,我有两个问题:

  1. iterator的构造函数应该公开吗? trie<T>有必要的begin()end()成员,当然还有trie<T>::iteratortrie<T>是共同的朋友,但我不知道惯例是什么。将它们设为私有将解决我从迭代器的构造函数中删除const“promise”所带来的许多烦恼。
  2. 关于迭代器及其const指针的正确node语义/约定是什么?没有人向我解释这个,我找不到任何网上的教程或文章。这可能是更重要的问题,但它确实需要大量的规划和正确的实施。我想只要实现1就可以规避它,但这是事情的原则

3 个答案:

答案 0 :(得分:2)

1)所有迭代器都需要是可复制构造的。你的迭代器是双向的,因此也需要默认构造(http://en.cppreference.com/w/cpp/concept/ForwardIterator),虽然我不知道为什么。因此默认构造函数需要是公共的,但您可以使用const trie<T>*来执行您喜欢的操作。我认为它应该是私有的,因为这个类的目的是为用户提供一个迭代器而不是trie,因此它的公共接口应该只是相应类别的迭代器的接口。不需要任何额外的公共建设者。

2)erase是非const函数。您可以有效传递给它的唯一迭代器是迭代器,它引用调用该函数的同一个trie,这意味着(我认为,虽然我不太确定我是否遵循了您的设计)但父母的整个层次结构都是非const对象。所以我怀疑这是你可以const_cast<trie<T>*>(it.parents.top().node)的情况之一。 iterator 不允许使用它来修改trie,这就是你希望它保存指向const的指针的原因。但是当你持有一个指向trie的非const指针,即this时,你可以修改你喜欢的任何部分,而迭代器只是给你开始修改的位置。

您可以在此处绘制一些更为通用的const安全原则。 container::erase(const_iterator)函数中的一个可能情况是,您从迭代器获得的const container*等于this。在这种情况下,const_cast肯定既安全又合法(也是不必要的,因为你可以只使用this,但这与它是否为const-correct相关。在您的容器中,它(通常)不等于this,它指向几个trie对象中的一个,它们共同构成this所属的分层容器。好消息是,整个容器在逻辑上是const或逻辑上是非const的,因此const_cast就像它是一个对象一样安全和合法。但有点难以证明是正确的,因为你必须确保在你的设计中,整个分层容器确实如我所假设的那样,共享非常数。

答案 1 :(得分:1)

希望修改const指向的trie的{​​{1}}非const_iterator方法(它指向this trie const_cast个实例)应该只需要const。您知道这是一个安全且定义好的演员,因为如果有人设法调用此trie的非trie实例,则此const的实例本身不是const。 *

或者,您可以执行相反的操作并将非trie指针放在const_iterator内的const_cast,这在构造时需要const_iterator。出于同样的原因,这是安全的。 const方法只提供trie访问权限,因此用户无法改变迭代器指向的const_iterator部分。如果const需要在非trie trie方法中进行变异,那么就可以了,因为const首先不能是const_cast。 *

这里const的安全性的前提是,只要不改变对象,就可以安全地保持并使用非const指向const对象的指针。宾语。抛出一个指针的const,指向最初未声明为const的东西是安全的。

*是的,非trie const_cast方法的来电者可能会以未定义的方式进行trie编辑;但在这种情况下,引起不确定行为的问题就在他们头上,而不是{{1}} s。

答案 2 :(得分:0)

  1. iterator的构造函数应该公开吗?
  2. 至少是复制构造函数,是的。看一下这个描述每种类型的迭代器应具有的特征的图表:

    http://www.cplusplus.com/reference/iterator/

    所有迭代器类型都应该是 copy-constructible,copy-assignable和destructible ,这意味着它们需要公共拷贝构造函数。一些迭代器,例如RandomAccessIterator也应该是默认的可构造,因此默认构造函数也应该是公共的。

    1. 这里有关于迭代器及其const指针的正确node语义/约定是什么?
    2. 如果你想拥有erase,那么你真的没有const_iterator,那么你就会有一个iterator。两者之间的区别在于,如果您有一个const trie对象,那么您只能获得const_iterator,因为您不允许以任何方式修改它。 / p>

      您可以在STL容器中注意到这一点。他们往往都有:

      iterator begin();
      const_iterator begin() const;
      

      通常情况下,您实施const_iterator然后:

      class iterator : public const_iterator {...};
      

      实现一个或两个非const函数。这可能意味着只有erase,因为您的operator*将保持不变。