本を参照せずに、誰でもCRTP
コード例を使って良い説明を提供できますか?
本を参照せずに、誰でもCRTP
コード例を使って良い説明を提供できますか?
回答:
要するに、CRTPは、クラスA
がそのクラスA
自体のテンプレート特殊化である基本クラスを持っている場合です。例えば
template <class T>
class X{...};
class A : public X<A> {...};
それはされて不思議なことではない、定期的な?:)
さて、これはあなたに何を与えますか?これにより、実際にX
テンプレートはその特殊化の基本クラスになることができます。
たとえば、次のようなジェネリックシングルトンクラス(簡易バージョン)を作成できます。
template <class ActualClass>
class Singleton
{
public:
static ActualClass& GetInstance()
{
if(p == nullptr)
p = new ActualClass;
return *p;
}
protected:
static ActualClass* p;
private:
Singleton(){}
Singleton(Singleton const &);
Singleton& operator = (Singleton const &);
};
template <class T>
T* Singleton<T>::p = nullptr;
ここで、任意のクラスA
をシングルトンにするには、これを行う必要があります
class A: public Singleton<A>
{
//Rest of functionality for class A
};
ご覧のように?シングルトンテンプレートは、あらゆるタイプの特殊化X
が継承されることを前提としているためsingleton<X>
、GetInstance
!CRTPには他にも便利な使い方があります。たとえば、クラスに現在存在するすべてのインスタンスをカウントしたいが、このロジックを別のテンプレートにカプセル化したい場合(具象クラスのアイデアは非常に単純です-静的変数を持ち、ctorでインクリメントし、dtorでデクリメントします)。練習としてやってみてください!
Boostのさらにもう1つの有用な例(私は彼らがどのようにそれを実装したかはわかりませんが、CRTPもそうします)。<
クラスにはオペレーターのみを提供し、クラスには自動的にオペレーターを提供したいと想像してください==
!
あなたはこのようにそれを行うことができます:
template<class Derived>
class Equality
{
};
template <class Derived>
bool operator == (Equality<Derived> const& op1, Equality<Derived> const & op2)
{
Derived const& d1 = static_cast<Derived const&>(op1);//you assume this works
//because you know that the dynamic type will actually be your template parameter.
//wonderful, isn't it?
Derived const& d2 = static_cast<Derived const&>(op2);
return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}
これでこのように使えます
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
今、あなたは明示的に演算子==
を提供していませんApple
か?しかし、あなたはそれを持っています!あなたは書ける
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
これは、の演算子==
を記述しただけの場合は少なく書くように見えるかもしれませんが、テンプレートが、だけでなく、などを提供するApple
と想像してEquality
ください。そして、これらの定義を複数のクラスに使用して、コードを再利用できます!==
>
>=
<=
CRTPは素晴らしいものです:) HTH
ここにあなたは素晴らしい例を見ることができます。仮想メソッドを使用する場合、プログラムは実行時に何が実行されるかを認識します。CRTPを実装すると、コンパイラーがコンパイル時に決定します。これは素晴らしいパフォーマンスです!
template <class T>
class Writer
{
public:
Writer() { }
~Writer() { }
void write(const char* str) const
{
static_cast<const T*>(this)->writeImpl(str); //here the magic is!!!
}
};
class FileWriter : public Writer<FileWriter>
{
public:
FileWriter(FILE* aFile) { mFile = aFile; }
~FileWriter() { fclose(mFile); }
//here comes the implementation of the write method on the subclass
void writeImpl(const char* str) const
{
fprintf(mFile, "%s\n", str);
}
private:
FILE* mFile;
};
class ConsoleWriter : public Writer<ConsoleWriter>
{
public:
ConsoleWriter() { }
~ConsoleWriter() { }
void writeImpl(const char* str) const
{
printf("%s\n", str);
}
};
virtual void write(const char* str) const = 0;
か?公平であるとはいえ、このテクニックwrite
は他の作業をしているときに非常に役立ちます。
CRTPは、コンパイル時のポリモーフィズムを実装する手法です。これは非常に単純な例です。以下の例でProcessFoo()
は、はBase
クラスインターフェイスを操作しBase::Foo
、派生オブジェクトのfoo()
メソッドを呼び出します。これは、仮想メソッドで行うことを目的としています。
http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e
template <typename T>
struct Base {
void foo() {
(static_cast<T*>(this))->foo();
}
};
struct Derived : public Base<Derived> {
void foo() {
cout << "derived foo" << endl;
}
};
struct AnotherDerived : public Base<AnotherDerived> {
void foo() {
cout << "AnotherDerived foo" << endl;
}
};
template<typename T>
void ProcessFoo(Base<T>* b) {
b->foo();
}
int main()
{
Derived d1;
AnotherDerived d2;
ProcessFoo(&d1);
ProcessFoo(&d2);
return 0;
}
出力:
derived foo
AnotherDerived foo
foo()
のものです。つまり、派生クラスによって実装される機能を強制します。
ProcessFoo()
関数で役立つ理由も示しているため、私のお気に入りの答えです。
void ProcessFoo(T* b)
わかりません。DerivedとAnotherDerivedが実際に派生していてもいなくても、引き続き機能するからです。私見ProcessFooが何らかの形でテンプレートを使用しなかった場合は、さらに興味深いでしょう。
ProcessFoo()
されたものは、インターフェイスを実装する任意のタイプで機能します。つまり、この場合、入力タイプTにはと呼ばれるメソッドが必要foo()
です。第2に、テンプレート化さProcessFoo
れていないものを複数のタイプで機能させるために、RTTIを使用することになりますが、これは避けたいものです。さらに、テンプレート化されたバージョンは、インターフェースでコンパイル時間チェックを提供します。
これは直接的な回答ではなく、CRTPがどのように役立つかを示す例です。
良い具体例CRTPはあるstd::enable_shared_from_this
C ++ 11から。
クラス
T
はを継承して、enable_shared_from_this<T>
を指すインスタンスshared_from_this
を取得するメンバー関数を継承できshared_ptr
ます*this
。
つまり、から継承std::enable_shared_from_this
すると、インスタンスへのアクセスなしでインスタンスへの共有(または弱い)ポインタを取得できます(例:知っているだけのメンバー関数から)*this
。
を与える必要があるstd::shared_ptr
が、アクセスできるのは*this
次の場合のみです:
struct Node;
void process_node(const std::shared_ptr<Node> &);
struct Node : std::enable_shared_from_this<Node> // CRTP
{
std::weak_ptr<Node> parent;
std::vector<std::shared_ptr<Node>> children;
void add_child(std::shared_ptr<Node> child)
{
process_node(shared_from_this()); // Shouldn't pass `this` directly.
child->parent = weak_from_this(); // Ditto.
children.push_back(std::move(child));
}
};
this
代わりに直接渡すことができない理由shared_from_this()
は、所有権メカニズムを壊すためです。
struct S
{
std::shared_ptr<S> get_shared() const { return std::shared_ptr<S>(this); }
};
// Both shared_ptr think they're the only owner of S.
// This invokes UB (double-free).
std::shared_ptr<S> s1 = std::make_shared<S>();
std::shared_ptr<S> s2 = s1->get_shared();
assert(s2.use_count() == 1);
ちょうど注意してください:
CRTPは、静的なポリモーフィズム(動的なポリモーフィズムに似ていますが、仮想関数ポインターテーブルがない)を実装するために使用できます。
#pragma once
#include <iostream>
template <typename T>
class Base
{
public:
void method() {
static_cast<T*>(this)->method();
}
};
class Derived1 : public Base<Derived1>
{
public:
void method() {
std::cout << "Derived1 method" << std::endl;
}
};
class Derived2 : public Base<Derived2>
{
public:
void method() {
std::cout << "Derived2 method" << std::endl;
}
};
#include "crtp.h"
int main()
{
Derived1 d1;
Derived2 d2;
d1.method();
d2.method();
return 0;
}
出力は次のようになります:
Derived1 method
Derived2 method
vtable
CRTPを使用せずにsなしで実行できます。がvtable
実際に提供するのは、基本クラス(ポインターまたは参照)を使用して派生メソッドを呼び出すことです。ここで、CRTPでどのように行われるかを示す必要があります。
Base<>::method ()
は、は呼び出されていませんし、どこでもポリモーフィズムを使用していません。
methodImpl
中method
のBase
派生クラスでは、名前methodImpl
の代わりにmethod