11

楽しみと利益のために、C++ で trie クラスを作成しています (C++11 標準を使用)。

trie<T>にはイテレータがありますtrie<T>::iterator。(トライの を変更することはできないため、これらはすべて実際には機能的 には です。) イテレータのクラス宣言は、部分的に次のようになります。const_iteratorvalue_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消去するには to データを使用します (iterator次のオブジェクトに to を返します)。

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、イテレータが指すトライを変更するべきではありませんが、イテレータが指すトライを変更する必要があります。

だから、私は2つの質問があります:

  1. iteratorのコンストラクターは公開する必要がありますか? trie<T>には必要なbegin()andend()メンバーがいて、もちろんtrie<T>::iteratorandtrie<T>は相互の友人ですが、慣習が何であるかはわかりません。constそれらを非公開にすることで、イテレーターのコンストラクターから「約束」を削除することについて私が抱えている多くの不安が解決されます。
  2. constここでのイテレータとそのnodeポインタに関する正しいセマンティクス/規則は何ですか? 誰もこれについて私に説明したことはありませんし、Web 上でチュートリアルや記事を見つけることもできません。これはおそらくより重要な質問ですが、十分な計画と適切な実装が必要です。1を実装するだけで回避できると思いますが、それが原則です!
4

3 に答える 3

2

1) すべての反復子はコピー構築可能である必要があります。イテレータは双方向であるため、理由はわかりませんが、デフォルトで構築可能である必要があります( http://en.cppreference.com/w/cpp/concept/ForwardIterator )。したがって、デフォルトのコンストラクターはパブリックである必要がありますが、好きなことを行うことができますconst trie<T>*。このクラスの目的はトライを介してユーザーに反復子を提供することであり、その公開インターフェイスは適切なカテゴリの反復子のインターフェイスのみであるべきであるため、プライベートにする必要があると思います。追加のパブリック コンストラクターは必要ありません。

2)eraseは非 const 関数です。有効に渡すことができる唯一の反復子は、関数が呼び出されたのと同じトライを参照する反復子です。つまり、(私はあなたの設計に従っているとは確信していませんが) 親の階層全体は非 const オブジェクト。だから、これはあなたができるケースの1つであると思いますconst_cast<trie<T>*>(it.parents.top().node). イテレータは、それを使用してトライを変更することは許可されていません。そのため、const へのポインタを保持する必要があります。ただし、トライへの非 const ポインター、つまり を保持するthisと、好きな部分を変更することができ、イテレーターは変更を開始する位置を提供するだけです。

ここで描くことができる const-safe のより一般的な原則があるかもしれません。container::erase(const_iterator)関数で考えられるケースの 1 つconst container*は、イテレータから取得する が と等しい場合thisです。その場合、const_castは確かに安全で正当なものです (また、 をそのまま使用できるため不要ですがthis、それが const-correct であるかどうかは問題ではありません)。あなたのコンテナでは、それは(一般的に)と等しくなく、一緒に階層コンテナを構成するthisいくつかのオブジェクトの1つを指しています。良いニュースは、コンテナー全体が論理的に const または論理的に非 const であるということです。したがって、triethisconst_castすべてが 1 つのオブジェクトであるかのように、安全で正当なものです。しかし、正しいことを証明するのは少し難しいです。なぜなら、私が仮定したように、階層型コンテナー全体が実際に非定数を共有していることを設計で確認する必要があるからです。

于 2013-10-22T20:54:37.467 に答える
0
  1. iteratorのコンストラクターは公開する必要がありますか?

少なくともコピー コンストラクターは、はい。各タイプのイテレータが持つべき特性を説明するこのチャートを見てください。

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

すべてのイテレータ型は、コピー構築可能、コピー割り当て可能、および破壊可能である必要があるため、パブリック コピー コンストラクタが必要です。たとえば、一部のイテレータRandomAccessIteratorデフォルトで構築可能にする必要があるため、デフォルトのコンストラクタもパブリックにする必要があります。

  1. constここでのイテレータとそのnodeポインタに関する正しいセマンティクス/規則は何ですか?

を持ちたい場合はerase、実際には を持っていません。const_iterator通常の を持っていますiterator。この 2 つの違いは、オブジェクトがある場合、それを変更することは一切許可されていないためconst trie、そのオブジェクトから取得できるのは 1 つだけであるということです。const_iterator

これは、STL コンテナーで確認できます。彼らは両方を持っている傾向があります:

iterator begin();
const_iterator begin() const;

通常起こることは、const_iteratorthen を実装することです:

class iterator : public const_iterator {...};

これは、1 つまたは 2 つの非 const 関数を実装します。あなたは const のままになるので、これはおそらくeraseあなたのためだけに意味します。operator*

于 2013-10-22T20:49:41.013 に答える