C ++:リーシュパターン

先日、同僚がそのようなタスクを投げました:

人間犬の 2種類のオブジェクトがあります。 人間は犬を飼っている (または飼っていない)場合があります。 ホストを持っているかもしれません(持っていないかもしれません)。 Human型のオブジェクトがDog型のオブジェクトを所有している場合、 Dog型のこのオブジェクトについては、 Human型のこのオブジェクトがマスターであり、彼だけであること明らかです。 そして、 は彼の人間が誰であるか、そしてその逆も知っているべきです。 どのように実装しますか?」

すべてがシンプルに思えます。HumanクラスとDogクラスで相互に2つのポインタを取得してみましょう。 しかし、このベンチャーの実装により、新しいデザインパターンのアイデアが生まれました。

テンプレートでない場合は、少なくとも静的な型制御といくつかの便利な「便利な機能」を備えたプログラムで双方向リンクを使用できるC ++イディオム。

注: 「リンク」という用語は、 「C ++リンク」の意味ではなく、「接続」の意味で記事で使用されています。

まず、ポインターを使用した最初のアイデアが悪いものを見てみましょう。

リーシュv.1(「額に」)


class Dog; class Human; class Dog { Human* link; public: Dog(): link(NULL) {} void SetLink(Human* l) { link = l; } const Human* GetLink() const { return link; } } class Human { Dog* link; public: Human(): link(NULL) {} void SetLink(Dog* l) { link = l; } const Dog* GetLink() const { return link; } } 


このような実装により、「額に入れて」と言われるように、問題を解決できます。
 Human *h = new Human(); Dog *d = new Dog(); h->SetLink(d); d->SetLink(h); 


ただし、明らかな欠点がいくつかあります。
  1. バックリンクの設定を忘れることがあります: d->SetLink(h)
  2. 他のオブジェクトへの逆方向リンクを誤って設定する可能性があります: d->SetLink(h2)
  3. 関連オブジェクトの1つを破棄した後、他のオブジェクトは破棄されたオブジェクトを参照します。

これらの欠点を取り除くのは簡単です。

Leash v.2(自動)


 class Dog; class Human; class Dog { Human* link; public: Dog(): link(NULL) {} ~Dog() { if (link) link->SetLink(NULL); } void SetLink(Human* l) { if (link == l) return; Human* oldlink = link; link = NULL; if (oldlink) oldlink->SetLink(NULL); link = l; if (link) link->SetLink(this); } const Human* GetLink() const { return link; } } class Human { Dog* link; public: Human(): link(NULL) {} ~Human() { if (link) link->SetLink(NULL); } void SetLink(Dog* l) { if (link == l) return; Dog* oldlink = link; link = NULL; if (oldlink) oldlink->SetLink(NULL); link = l; if (link) link->SetLink(this); } const Dog* GetLink() const { return link; } } 

何が変わった?

まず、 SetLink()メソッドがSetLink()複雑になりました。 これで、1つの方向にリンクを設定すると、メソッドは自動的に他の方向にリンクを設定します。 リンクの1つをインストールすることを忘れることはできません。 間違ったバックリンクを設定することは不可能です。
 Human *h1, *h2; h1 = new Human(); h2 = new Human(); Dog *d = new Dog(); h1->SetLink(d); assert(h1->GetLink()->GetLink() == h1); //passed (d    h1) d->SetLink(h2); assert(d->GetLink()->GetLink() == d); //passed (h2    d) assert(h1->GetLink() == NULL); //passed (h1    d) 

第二に、オブジェクトが破棄されると、関連するオブジェクトがあればリンクを自動的に削除するデストラクタが登場しました。 したがって、 GetLink() 常に有効なポインターまたはNULLを期待できます。

さて、今、それはすべての欠点が取り除かれ、あなたがそれを使用できるように思えます。 ただし、注意深い読者は、 HumanクラスとDogクラスの実装が同一であり、使用されるタイプのみが異なることに気付くでしょう。 そして、私は考えました:「テンプレートのためにこれをやり直す必要がある!」と言われています-完了しました。

Leash v.3(テンプレート)


テンプレートには、1つ以上のタイプのパラメーター化が含まれます。 この場合、これらのタイプにはM (My)とL (Link)の2つがあり、テンプレート自体はそれぞれO <M、L>と記述されます。 このようなテンプレートの特殊化のインスタンスは、タイプLのオブジェクトへのポインターを格納できます。つまり、接続M-> Lを編成します クラスAとクラスBの間の双方向通信を作成するために、O <A、B>O <B、A>という 2つの対称的な専門化が使用されます。

したがって、タイプMはリンクの内側の端のタイプを示し、 thisポインターを正しくキャストするために必要です。タイプLはリンクの外側の端のタイプを示します。
 template<class M, class L> class O { L* link; public: O(): link(NULL) {} ~O() { if (link) link->SetLink(NULL); } void SetLink(L* l) { if (link == l) return; L* oldlink = link; link = NULL; if (oldlink) oldlink->SetLink(NULL); link = l; if (link) link->SetLink(static_cast<M*>(this)); } const L* GetLink() const { return link; } }; 

このテンプレートクラスの使用は非常に簡単です。
 class Human; class Dog; class Human: public O<Human, Dog> {}; class Dog: public O<Dog, Human> {}; Human* h = new Human(); Dog* d = new Dog(); h->SetLink(d); 

いいね! クラスを2倍短く記録しました!

この双方向リンクの実装の優れた機能は、コンパイラがいくつかの論理エラーを判断できることです。

たとえば、リンクの逆数であるクラスが存在しない。 つまり、リンクHuman- > Dogを含むことができるクラスがコードに含まれているが、リンクDog- > Humanを含むことができるクラスがない場合、このコードはコンパイルされません。
 template<class M, class L> class O { ... }; class Human; class Dog; class Human: public O<Human, Dog> {}; class Dog {}; 


もちろん、同じタイプのオブジェクトをバインドすることは許可されています。
 template<class M, class L> class O { ... }; class Human: public O<Human, Human> {}; Human *h1, *h2; ... h1->SetLink(h2); 


自分へのリンクを設定することもできます。 これが受け入れられない場合、それにSetLink()SetLink()メソッドを修正するのは簡単です。

今ではとても良いようです! しかし、 Dogオブジェクトへの参照に加えて、 Catオブジェクトへの参照に人間が必要な場合はどうでしょうか?

リーシュv.4(複数のリンク)


これは、多重継承とわずかに変更されたOクラスを使用して簡単に配置することもできます。
 template<class M, class L> class O { L* link; public: O(): link(NULL) {} ~O() { if (link) link->O<L,M>::SetLink(NULL); } void SetLink(L* l) { if (link == l) return; L* oldlink = link; link = NULL; if (oldlink) oldlink->O<L,M>::SetLink(NULL); link = l; if (link) link->O<L,M>::SetLink(static_cast<M*>(this)); } const L* GetLink() const { return link; } }; class Human; class Dog; class Cat; class Human: public O<Human, Dog>, public O<Human, Cat> {}; class Dog: public O<Dog, Human> {}; class Cat: public O<Cat, Human> {}; 

SetLink関数が正しい基本クラスからSetLinkように、 O <L、M> SetLinkの明示的な解決SetLinkた。 熱心な読者は、オーバーロードされた関数の場合のように、渡されたパラメーターのタイプによってコンパイラーがSetLink()正しいSetLink()関数を選択していることに気付くでしょう。 ただし、これらの関数が異なるクラス(この場合は異なるクラス)にある場合、C ++標準に従って、オーバーロードは機能しません[1]。

同じ変更は、クラスのユーザーに影響します。
 Human* h; Dog* d; Cat* c; ... // h->SetLink(d); // ,  h->O<Human, Dog>::SetLink(d); //,   h->O<Human, Cat>::SetLink(c); //      //   GetLink() // h->GetLink(); //,  Link   -      h->O<Human, Dog>::GetLink(); //,   

これは、 Humanクラスを少し改良することで簡素化できます。 タイプに応じて親クラスの対応するメソッドを呼び出す独自のメソッドSetLink()およびGetLink()のテンプレート実装を追加します。
 class Human: public O<Human, Dog>, public O<Human, Cat> { public: template<class T> const T* GetLink() const { return O<Human,T>::GetLink(); } template<class T> void SetLink(T* l) { O<Human,T>::SetLink(l); } }; 

これで、すべてが単一の接続とほぼ同じくらい簡単になりました。
 Human* h; Dog* d; Cat* c; ... h->SetLink<Dog>(d); h->SetLink<Cat>(c); //   ,     h->SetLink(d); h->SetLink(c); h->GetLink<Dog>(); //  Dog h->GetLink<Cat>(); //  Cat 


どうしたの?


私の意見では、便利なテンプレートクラスであることが判明しました。これにより、厳密に定義されたタイプのオブジェクト間で、 整合性を自動的に維持しながら双方向通信を確立できます。

はい、テンプレートクラスを文字Oと呼んだ理由を聞かないでください 何よりも、 リーシュ (リーシュ)という名前が彼に合っているでしょう。

書かれたものはgcc 4.4.3を使用してテストされました。

参照資料


[1] bytes.com/topic/c/answers/137040-multiple-inheritance-ambiguity

PS。 誰かが現在のC ++標準のオンラインバージョンを知っている場合は、リンクを共有してください。元のソースを参照したいと思います。

PPS このトピックはSandboxでの遅い火事に苦しんでいましたが、複数のリンク、さらにC ++ 0xを使用する場合にOの使用を簡素化する方法のアイデアを得ました。 お楽しみに。

Source: https://habr.com/ru/post/J126501/


All Articles