4

(注:このカテゴリの問題を読むための適切な検索用語に関する提案を実際に探しています。 「オブジェクト リレーショナル マッピング」は、優れた先行技術を見つけることができる場所として思い浮かびました...しかしこのシナリオに完全に適合するものはまだ見たことがありません。)

私は非常に一般的な を持っていclass Nodeますが、これは今のところ DOM ツリーの要素のようなものと考えることができます。これは実際に起こっていることではありません。メモリ マップ ファイル内のグラフ データベース オブジェクトです。しかし、この類推はすべての実用的な目的でかなり近いので、簡単にするために DOM 用語に固執します。

ノードに埋め込まれた「タグ」は、(理想的には) ノードで実行できる特定の操作セットを意味します。現在、これを行うために派生クラスを使用しています。たとえば、HTML リストのようなものを表現しようとしていた場合:

<ul>
   <li>Coffee</li>
   <li>Tea</li>
   <li>Milk</li>
</ul>

基になるツリーは 7 つのノードになります。

+--UL                       // Node #1
   +--LI                    // Node #2
      +--String(Coffee)     // Node #3 (literal text)
   +--LI                    // Node #4
      +--String(Tea)        // Node #5 (literal text)
   +--LI                    // Node #6
      +--String(Milk)       // Node #7 (literal text)

getString()はすでにノード自体のプリミティブ メソッドであるため、おそらく , だけを作成しclass UnorderedListNode : public Nodeますclass ListItemNode : public Node

この仮説を続けて、プログラマーが手元にあるノードの「タイプ」/タグについて詳しく知っているときに、プログラマーがあまり一般的でない機能を使用できるようにしたいと考えてみましょう。おそらく、順序付けられていないリストに文字列アイテムを追加したり、文字列として抽出したりするなど、ツリーの構造イディオムで彼らを支援したいと思います。 (これは単なる例えですので、ルーチンをあまり真剣に考えないでください。)

class UnorderedListNode : public Node {
private:
    // Any data members someone put here would be a mistake!

public:
    static boost::optional<UnorderedListNode&> maybeCastFromNode(Node& node) {
        if (node.tagString() == "ul") {
            return reinterpret_cast<UnorderedListNode&>(node);
        }
        return boost::none;
    }

    // a const helper method
    vector<string> getListAsStrings() const {
        vector<string> result;
        for (Node const* childNode : children()) {
            result.push_back(childNode->children()[0]->getText());
        }
        return result;
    }

    // helper method requiring mutable object
    void addStringToList(std::string listItemString) {
        unique_ptr<Node> liNode (new Node (Tag ("LI"));
        unique_ptr<Node> textNode (new Node (listItemString));
        liNode->addChild(std::move(textNode));
        addChild(std::move(liNode));
    }
};

これらの新しい派生クラスにデータ メンバーを追加することは、悪い考えです。情報を実際に永続化する唯一の方法は、ノードの基本ルーチン (たとえば、addChild上記の呼び出しまたはgetText) を使用してツリーと対話することです。したがって、実際の継承モデルは (存在する範囲で) C++ 型システムの外部にあります。<UL>ノード「maybeCast」UnorderedListNodeを vtables/etc とは何の関係もありません。

C++ の継承は、正しいように見えることもありますが、通常は間違っていると感じます。継承の代わりに、Node とは独立して存在するクラスを用意し、「アクセサ ヘルパー」として何らかの方法で Node と連携する必要があるように感じます...しかし、それがどのようなものになるかはよくわかりません。

4

2 に答える 2

0

「C++の継承は時々正しいように見えますが、通常は間違っていると感じます。」

確かに、そしてこの声明は気になります:

ノードを「maybeCast」にしてUnorderedListNodeにするものは、vtables/etcとは何の関係もありません。

このコードは次のとおりです。

static boost::optional<UnorderedListNode&> maybeCastFromNode(Node& node) {
    if (tagString() == "ul") {
        return reinterpret_cast<UnorderedListNode&>(node);
    }
    return boost::none;
}

(1)

Node&渡されるものが、継承パス上に合法かつ適切に構築されなかったメカニズムを介して割り当てられた場合UnorderedListNode、これはいわゆる型のパンニングです。ほとんどの場合、それは悪い考えです。ほとんどのコンパイラのメモリレイアウトは、仮想関数がなく、派生クラスがデータメンバーを追加しないときに機能しているように見えても、ほとんどすべての状況で自由に壊すことができます。

(2)

次に、根本的に異なるタイプのオブジェクトへのポインタは互いに「エイリアス」しないというコンパイラの仮定があります。これは厳密なエイリアシング要件です。非標準の拡張機能を介して無効にすることができますが、それはレガシー状況でのみ適用する必要があります...それは最適化を妨げます。

これらの2つの障害が、特別な状況下で仕様によって許可された回避策を持っているかどうかは、学術的な観点からは完全には明らかではありません。これはそれを調査する質問であり、執筆時点ではまだオープンな議論です:

新しいオブジェクトを割り当てることなく、ポインタキャストのみを介して交換可能なクラスタイプを作成しますか?

しかし、@MatthieuMを引用します。「仕様の端に近づくほど、コンパイラのバグに遭遇する可能性が高くなります。したがって、エンジニアとして、実用的であり、コンパイラでマインドゲームをプレイすることは避けてください。正しいか間違っているかは関係ありません。無関係:本番コードでクラッシュが発生すると、コンパイラの作成者ではなく、負けます。」

これはおそらくより正しい道です:

継承の代わりに、Nodeとは独立して存在するクラスが必要で、「アクセサーヘルパー」として何らかの形でコラボレーションする必要があるように感じます...しかし、それがどのようなものになるかはよくわかりません。

デザインパターンの用語を使用すると、これはプロキシのようなものと一致します。ポインタを格納し、値で渡される軽量オブジェクトがあります。const実際には、ラップされている着信ポインタの性質についてどうするかなどの問題を処理するのは難しい場合があります。

これは、この場合に比較的簡単に実行できる方法のサンプルです。まず、Accessor基本クラスの定義:

template<class AccessorType> class Wrapper;

class Accessor {
private:
    mutable Node * nodePtrDoNotUseDirectly;
template<class AccessorType> friend class Wrapper;
    void setNodePtr(Node * newNodePtr) {
        nodePtrDoNotUseDirectly = newNodePtr;
    }
    void setNodePtr(Node const * newNodePtr) const {
        nodePtrDoNotUseDirectly = const_cast<Node *>(newNodePtr);
    }
    Node & getNode() { return *nodePtrDoNotUseDirectly; }
    Node const & getNode() const { return *nodePtrDoNotUseDirectly; }

protected:
    Accessor() {}

public:
    // These functions should match Node's public interface
    // Library maintainer must maintain these, but oh well
    inline void addChild(unique_ptr<Node>&& child)) { 
        getNode().addChild(std::move(child));
    }
    inline string getText() const { return getNode().getText(); }
    // ...
};

const Node &次に、「const Accessor」をラップする場合を処理するための部分的なテンプレートの特殊化。これは、 :を受信することを通知する方法です。

template<class AccessorType>
class Wrapper<AccessorType const> {    
protected:
    AccessorType accessorDoNotUseDirectly;
private:
    inline AccessorType const & getAccessor() const {
        return accessorDoNotUseDirectly;
    }

public:
    Wrapper () = delete;
    Wrapper (Node const & node) { getAccessor().setNodePtr(&node); }
    AccessorType const * operator-> const () { return &getAccessor(); }
    virtual ~Wrapper () { }
};

「可変アクセサー」の場合のラッパーは、独自の部分的なテンプレートの特殊化を継承します。このようにして、継承は適切な強制と割り当てを提供します... constのnon-constへの割り当てを禁止しますが、逆の方法で機能します。

template<class AccessorType>
class Wrapper : public Wrapper<AccessorType const> {
private:
    inline AccessorType & getAccessor() {
        return Wrapper<AccessorType const>::accessorDoNotUseDirectly;
    }

public:
    Wrapper () = delete;
    Wrapper (Node & node) : Wrapper<AccessorType const> (node) { }
    AccessorType * operator-> () { return &Wrapper::getAccessor(); }
    virtual ~Wrapper() { }
};

テストコードと奇妙な部分を文書化したコメントを含むコンパイルの実装は、ここの要点にあります


出典:@MatthieuM。@ PaulGroke

于 2012-07-14T02:03:46.450 に答える
0

あなたが何をしようとしているのかを完全に理解できたかどうかはわかりませんが、役に立つかもしれないいくつかの提案をここに示します。

あなたは間違いなく継承で正しい軌道に乗っています。すべての UL ノード、LI ノードなどは Node-s です。完全な「is_a」関係。これらのクラスは Node クラスから派生させる必要があります。

プログラマーが手元にあるノードの「タイプ」/タグについて詳しく知っているときに、一般的ではない関数を使用できるようにしたいと考えてみましょう。

...そしてこれが仮想関数の目的です。

次にmaybeCastFromNode方法です。それはダウンキャストです。どうしてそうするか?多分デシリアライズのため?はいの場合は、を使用することをお勧めしdynamic_cast<UnorderedListNode *>ます。ただし、継承ツリーと仮想メソッドが適切に設計されている場合、RTTI はまったく必要ありません。

C++ の継承は、正しいように見えることもありますが、通常は間違っていると感じます。

これは必ずしも C++ のせいではないかもしれません :-)

于 2012-06-23T20:00:08.907 に答える