3

Curiously Recurring Template Pattern (CRTP) のデザイン パターンに「嫌悪感」があるのはなぜなのか、少し混乱しています。これは CRTP を使用して、各タイプのオブジェクトの配列を作成します。

私の質問:

なぜこれが悪いことなのですか?特に AutoLists のアイデアを対象としていますが、一般的な CRTP に関する回答で十分です。

私の意図は、各タイプのコンポーネントを簡単に分離できるように、エンティティ コンポーネント システムで使用することです。

4

3 に答える 3

9

C++ の継承は、次の 2 つの異なる目的を果たしてきました。

  1. ミックスイン (コードを複製せずに、新しいドロップイン動作をクラスに追加します)。
    このシナリオでは、基底クラス自体にはほとんど意味がありません。その目的は、新しい動作をサポートすることであり、すべてのサブクラス間で共通の基底クラスとして使用されることではありません。

  2. ポリモーフィズム (基本クラスで既に宣言されている動作の拡張)。
    このシナリオでは、基本クラスはすべてのサブクラスに共通のインターフェース yada yada を提供します。

CRTP は通常、最初の目的でvirtual使用され、2 番目の目的で使用されます。
この 2 つの違いを認識するのは簡単ではなく、ある程度の練習が必要です。

場合によっては、両方で同じことを達成できます。違いは、「ポリモーフィズム」が静的 (コンパイル時) か動的 (実行時) かだけです。
ランタイム ポリモーフィズムが必要ない場合は、一般に CRTP を使用します。コンパイラはコンパイル時に何が起こっているかを確認できるため、通常は高速だからです。

とはいえ、CRTP は広く使用されているため、「非常に嫌われている」とは言いたくありません。

于 2013-03-10T06:40:04.097 に答える
3

私は CRTP とその変形を C++、Java、C# の両方で広く使用してきました。「同僚からのフィードバック」から 1 つのことがわかります。多くの人は単純にそれを理解しておらず、「あまりにも複雑なくだらない"。

他の「複雑な」「新しい」メカニズムと同様に、誰かがそれを数回使用するまで、人々はその利点を理解するのが本当に難しいと感じます。

確かに、間違った場所で使用されることもあり、細部に細心の注意を払って使用する必要がありますが、それは重要なツールの命です。複数の継承と同じように、多くの人が嫌います。しかし、どうやってハンマーを憎むことができますか? 嫌いなことは何もありません。それができるという理由だけでなく、本当に有益な場所で適切に使用してください。

まず、本当にそれを使用する必要があるかどうかを再考してください。テンプレートの基本クラスは、正確な派生型を本当に知る必要がありますか? 仮想メンバーだけでは不十分ですか? それなしで逃げることはできますか?あなたの場合のメリットは何ですか?「より高いレベルのコード」をより短く、より読みやすく、より明白にするか、より拡張可能にするか、エラーを起こしにくくしますか?

多くの場合、ベースは正確な派生型を知る必要がなく、いくつかの仮想メソッドで置き換えることができます。しかし、これにより、他のユーザーにとってコード全体がより複雑になる可能性があります。一方、CRTP を使用すると、最終的なメカニズムはより「自動的」になり、実際には有益でない場合があります。

エンティティ クラスの場合、多くの場合、CRTP の一部のバリアントには実際には理由があります。ベースが「類似の」オブジェクトを返すいくつかのユーティリティ メソッドを公開している場合、それらのメソッドが「ObjectBase*」ではなく洗練された「MyObject*」を返すようにすることがよくあります。それなしでは達成するのは難しい。しかし、本当の問題は、これらのメソッドは、'factory'、'manager'、または 'storagecontext' 内ではなく、実際にエンティティの基本クラスにあるべきかということです。

于 2013-03-10T01:02:54.143 に答える
1

CRTP は、コンパイラがチェックできない CRTP クラスの使用に制限を導入します。つまり、タイプ セーフではない可能性があります。以下を例に取ります。

#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base() {}
    virtual Base *copy() = 0;
    virtual void SayHello() = 0;
};

template <typename Derived>
class BaseCopy: public Base {
public:
    virtual Base *copy()
    {
        return new Derived(static_cast<Derived const&>(*this));
    }
};

Base クラスの利用者が使用制限を知らずに宣言した場合

class Bar: public BaseCopy<Bar> { public: void SayHello(void) { cout << "Hello, I am Bar\n";} };
class Foo: public BaseCopy<Bar> { public: void SayHello(void) { cout << "Hello, I am Foo\n";} };

int main(void)
{
    Foo *foo = new Foo;
    Base *foo2 = foo->copy(); // What is foo2?
    foo->SayHello();
    foo2->SayHello();
    delete foo2;
    delete foo;

    return 0;
}

これを例えばでコンパイルします。g++

g++ -Wall -g main.cpp -o CRTP-test.exe

問題なくコンパイルされますfoo->copy();が、結果が Foo から構築された Bar になるため、呼び出すと未定義の動作が呼び出されます。

//jk

于 2013-03-10T00:22:27.817 に答える