Tanpa merujuk ke buku, dapatkah orang memberikan penjelasan yang baik CRTP
dengan contoh kode?
Tanpa merujuk ke buku, dapatkah orang memberikan penjelasan yang baik CRTP
dengan contoh kode?
Jawaban:
Singkatnya, CRTP adalah ketika sebuah kelas A
memiliki kelas dasar yang merupakan spesialisasi template untuk kelas A
itu sendiri. Misalnya
template <class T>
class X{...};
class A : public X<A> {...};
Hal ini anehnya berulang, bukan? :)
Sekarang, apa ini memberi Anda? Ini benar-benar memberi X
templat kemampuan untuk menjadi kelas dasar untuk spesialisasinya.
Misalnya, Anda bisa membuat kelas singleton generik (versi sederhana) seperti ini
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;
Sekarang, untuk membuat kelas arbitrer A
menjadi lajang Anda harus melakukan ini
class A: public Singleton<A>
{
//Rest of functionality for class A
};
Jadi kamu melihat? Templat tunggal mengasumsikan bahwa spesialisasi untuk jenis apa pun X
akan diwarisi dari singleton<X>
dan dengan demikian semua anggota (publik, yang dilindungi) dapat diakses, termasukGetInstance
! Ada kegunaan lain yang berguna dari CRTP. Misalnya, jika Anda ingin menghitung semua instance yang saat ini ada untuk kelas Anda, tetapi ingin merangkum logika ini dalam templat terpisah (ide untuk kelas konkret cukup sederhana - memiliki variabel statis, kenaikan dalam ctors, penurunan dalam dtors ). Cobalah untuk melakukannya sebagai latihan!
Contoh lain yang bermanfaat, untuk Boost (saya tidak yakin bagaimana mereka menerapkannya, tetapi CRTP juga akan melakukannya). Bayangkan Anda hanya ingin menyediakan operator <
untuk kelas Anda, tetapi secara otomatis operator ==
untuk mereka!
Anda bisa melakukannya seperti ini:
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 <
}
Sekarang Anda bisa menggunakannya seperti ini
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
Sekarang, Anda belum menyediakan operator secara eksplisit ==
untuk Apple
? Tetapi Anda memilikinya! Kamu bisa menulis
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
Hal ini bisa terlihat bahwa Anda akan menulis kurang jika Anda hanya menulis Operator ==
untuk Apple
, tapi bayangkan bahwa Equality
template yang akan memberikan tidak hanya ==
tetapi >
, >=
, <=
dll Dan Anda bisa menggunakan definisi ini untuk beberapa kelas, menggunakan kembali kode!
CRTP adalah hal yang luar biasa :) HTH
Di sini Anda dapat melihat contoh yang bagus. Jika Anda menggunakan metode virtual, program akan tahu apa yang dieksekusi di runtime. Mengimplementasikan CRTP, kompilerlah yang menentukan waktu kompilasi !!! Ini adalah kinerja yang luar biasa!
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;
? Meskipun harus adil, teknik ini tampaknya sangat membantu ketika write
melakukan pekerjaan lain.
CRTP adalah teknik untuk menerapkan polimorfisme waktu kompilasi. Ini contoh yang sangat sederhana. Dalam contoh di bawah ini, ProcessFoo()
bekerja dengan Base
antarmuka kelas dan Base::Foo
memanggil metode objek turunan foo()
, yang adalah apa yang ingin Anda lakukan dengan metode virtual.
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;
}
Keluaran:
derived foo
AnotherDerived foo
foo()
diimplementasikan oleh kelas turunan.
ProcessFoo()
fungsinya.
void ProcessFoo(T* b)
dan tanpa Derived dan AnotherDerived sebenarnya diturunkan masih akan berfungsi. IMHO akan lebih menarik jika ProcessFoo tidak memanfaatkan template.
ProcessFoo()
akan bekerja dengan semua jenis yang mengimplementasikan antarmuka yaitu dalam hal ini tipe input T harus memiliki metode yang disebut foo()
. Kedua, untuk mendapatkan non-templatized ProcessFoo
untuk bekerja dengan banyak jenis, Anda mungkin akan akhirnya menggunakan RTTI yang ingin kita hindari. Selain itu, versi templatized memberi Anda waktu kompilasi memeriksa pada antarmuka.
Ini bukan jawaban langsung, melainkan contoh bagaimana CRTP dapat bermanfaat.
Contoh konkret yang baik dari CRTP adalah std::enable_shared_from_this
dari C ++ 11:
Kelas
T
dapat mewarisi darienable_shared_from_this<T>
untuk mewarisishared_from_this
fungsi anggota yang mendapatkanshared_ptr
instance menunjuk ke*this
.
Artinya, mewarisi dari std::enable_shared_from_this
memungkinkan untuk mendapatkan pointer yang dibagikan (atau lemah) ke instance Anda tanpa akses ke sana (misalnya dari fungsi anggota di mana Anda hanya tahu tentang*this
).
Ini berguna ketika Anda perlu memberikan std::shared_ptr
tetapi Anda hanya memiliki akses ke *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));
}
};
Alasan Anda tidak bisa this
langsung lulus bukan shared_from_this()
karena itu akan merusak mekanisme kepemilikan:
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);
Sama seperti catatan:
CRTP dapat digunakan untuk mengimplementasikan polimorfisme statis (yang menyukai polimorfisme dinamis tetapi tanpa tabel penunjuk fungsi virtual).
#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;
}
Outputnya adalah:
Derived1 method
Derived2 method
vtable
s tanpa menggunakan CRTP. Apa yang vtable
sebenarnya disediakan adalah menggunakan kelas dasar (pointer atau referensi) untuk memanggil metode turunan. Anda harus menunjukkan cara melakukannya dengan CRTP di sini.
Base<>::method ()
bahkan tidak dipanggil, Anda juga tidak menggunakan polimorfisme di mana pun.
methodImpl
dalam method
dari Base
dan di kelas turunan nama methodImpl
bukanmethod