ทุกคนสามารถให้คำอธิบายที่ดีCRTP
กับตัวอย่างโค้ดโดยไม่ต้องอ้างอิงหนังสือหรือไม่
ทุกคนสามารถให้คำอธิบายที่ดีCRTP
กับตัวอย่างโค้ดโดยไม่ต้องอ้างอิงหนังสือหรือไม่
คำตอบ:
กล่าวโดยย่อ CRTP คือเมื่อคลาสA
มีคลาสพื้นฐานซึ่งเป็นเทมเพลตเฉพาะสำหรับคลาสA
นั้น เช่น
template <class T>
class X{...};
class A : public X<A> {...};
มันจะอยากรู้อยากเห็นที่เกิดขึ้นไม่ได้หรือไม่ :)
ทีนี้นี่ให้อะไรคุณ สิ่งนี้ทำให้X
แม่แบบความสามารถในการเป็นคลาสพื้นฐานสำหรับความเชี่ยวชาญ
ตัวอย่างเช่นคุณสามารถสร้างคลาส singleton ทั่วไป (เวอร์ชันที่ง่ายขึ้น) เช่นนี้
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 ตัวอย่างเช่นหากคุณต้องการนับอินสแตนซ์ทั้งหมดที่มีอยู่ในชั้นเรียนของคุณ แต่ต้องการสรุปแคปต์ตรรกะนี้ในเทมเพลตที่แยกต่างหาก (แนวคิดสำหรับคลาสคอนกรีตค่อนข้างง่าย - มีตัวแปรแบบคงที่ ) ลองทำแบบฝึกหัด!
อีกตัวอย่างที่มีประโยชน์สำหรับ Boost (ฉันไม่แน่ใจว่าพวกเขาใช้งานอย่างไร แต่ 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 เป็นเทคนิคในการใช้ polymorphism แบบรวบรวมเวลา นี่เป็นตัวอย่างง่ายๆ ในตัวอย่างด้านล่าง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)
และไม่มีการได้รับมาและ AnotherDerived จริงมามันจะยังคงทำงาน IMHO มันน่าสนใจกว่าถ้า ProcessFoo ไม่ได้ใช้ประโยชน์จากเทมเพลตอย่างใด
ProcessFoo()
จะทำงานร่วมกับประเภทใด ๆ ที่ดำเนินการอินเตอร์เฟซเช่นในกรณีนี้การป้อนข้อมูลประเภท T foo()
ควรจะมีวิธีการที่เรียกว่า ประการที่สองเพื่อให้ผู้ที่ไม่ได้รับ templatized 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 สามารถนำมาใช้ในการสร้าง polymorphism แบบคงที่ (เช่น polymorphism แบบไดนามิก แต่ไม่มีตารางตัวชี้ฟังก์ชันเสมือน)
#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
ต้อง s โดยไม่ต้องใช้ CRTP สิ่งที่vtable
จัดเตรียมอย่างแท้จริงคือการใช้คลาสฐาน (ตัวชี้หรือการอ้างอิง) เพื่อเรียกเมธอดที่ได้รับ คุณควรแสดงให้เห็นว่า CRTP ทำอะไรที่นี่
Base<>::method ()
ไม่มีการเรียกแม้แต่คุณไม่ใช้ความหลากหลายในทุกที่
methodImpl
ในmethod
ของBase
และในชั้นเรียนมาชื่อmethodImpl
แทนmethod