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,即使迭代器不应该这样。
所以,我有两个问题:
iterator
的构造函数应该公开吗? trie<T>
有必要的begin()
和end()
成员,当然还有trie<T>::iterator
和trie<T>
是共同的朋友,但我不知道惯例是什么。将它们设为私有将解决我从迭代器的构造函数中删除const
“promise”所带来的许多烦恼。const
指针的正确node
语义/约定是什么?没有人向我解释这个,我找不到任何网上的教程或文章。这可能是更重要的问题,但它确实需要大量的规划和正确的实施。我想只要实现1就可以规避它,但这是事情的原则!答案 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)
iterator
的构造函数应该公开吗? 至少是复制构造函数,是的。看一下这个描述每种类型的迭代器应具有的特征的图表:
http://www.cplusplus.com/reference/iterator/
所有迭代器类型都应该是 copy-constructible,copy-assignable和destructible ,这意味着它们需要公共拷贝构造函数。一些迭代器,例如RandomAccessIterator
也应该是默认的可构造,因此默认构造函数也应该是公共的。
const
指针的正确node
语义/约定是什么? 如果你想拥有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*
将保持不变。