2

私は独自のジェネリック ツリーの実装を作成しましたが、イテレータを作成するときに 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 オブジェクトをコンテナに入れると、これが問題を引き起こすかどうか疑問に思っていますか?

4

5 に答える 5

2

constを捨てないでください。意味が失われます!STLにconst_iteratorクラスとiteratorクラスがあるのには理由があります。constの正確性が必要な場合は、2つの別個のイテレータクラスを実装する必要があります。イテレータの目標の1つは、保存ポインタを模倣することです。したがって、反復範囲を超えている場合に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 iteratorクラスには、const要素へのポインターがあります。つまり、ポインタを変更することはできますが(そうでない場合は反復できません)、要素自体を変更することはできません。
  • whileループでイテレータをチェックする場合は、boolへの暗黙的な変換が実用的です。これにより、次のように書くことができます。もう一度ではwhile(it) { it++; }なくwhile(!it.IsDone()) { it++; }、古典的なポインターに似たもの。
  • 可能な場合はnullptrを使用します

私があなたの使用からわかるように、std::mapあなたはすでにSTLを使用しています。たぶん、既存のSTLイテレータを使用する方が簡単でしょうか?

于 2012-03-23T16:13:34.587 に答える
2

コンストラクターが を使用するためconst Item、オペレーターは const ポインターを返す必要があります。

非 const 項目を返したい場合は、コンストラクターに非 const パラメーターを使用する必要があります。解決策は、const オブジェクトを使用する基本クラスと、const 以外のオブジェクトを使用するサブクラスを用意することです (NSString や NSMutableString など、Objc で少し行われます)。

于 2012-03-23T15:44:16.063 に答える
1

通常、 aniteratorと aの 2 つの反復子バージョンを記述しますconst_iterator

Boost.Iterator ライブラリのドキュメントで公開されている、コードの重複を避けるためのテンプレート トリックがあります。非常に長いため、 Iterator Adapterがどのように定義されているかを確認してください。

BaseIterator問題は、意識的な aを記述してconstから、エイリアスを提供することです。

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

トリッキーは、変換コンストラクターを定義してIteratortoConstIteratorを可能にする方法にありますが、その逆はできません。

于 2012-03-23T15:48:55.293 に答える
1

本当の問題は、このコード/インターフェースが「嘘をついている」ことです。

コンストラクターは次のように述べています。私はあなた (ルート) を変更しません。しかし、イテレータは変更可能なアイテムを提供します。

「正直」に言うと、このクラス内で変更されない場合でも、コンストラクターが変更可能なルートを取るか、このクラスが変更可能なアイテムを提供しません。

ただし、Item 自体が変更可能な子を渡すかどうかを定義します。そのコードによってはコンパイルできない場合があります。多分これがあなたの建設の理由です。

長い話、短い意味: この設計は貧弱であり、変更する必要があります

おそらく、与えられた子の「const」性に応じて、2 つのテンプレートが必要です。

于 2012-03-23T16:04:27.147 に答える
1

const_castconst_casting 自体は常に安全ですが、宣言された ed 値に書き込もうとするconstと未定義の動作になります。

この場合、返されたポインターが宣言された値を指していてconst、それを変更しようとすると、未定義の動作が発生します。


経験則: const が正しくないコードとの相互運用以外に使用する必要がある場合、設計は壊れています。const_cast


標準は7.1.6.1 で言う cv-qualifiers :

ミュータブル (7.1.1) と宣言されたクラス メンバーを変更できることを除いて、その有効期間中 (3.8) に const オブジェクトを変更しようとすると、未定義の動作が発生します。

于 2012-03-23T15:37:19.573 に答える