Không cần tham khảo một cuốn sách, bất cứ ai cũng có thể vui lòng cung cấp một lời giải thích tốt cho CRTP
một ví dụ mã?
Không cần tham khảo một cuốn sách, bất cứ ai cũng có thể vui lòng cung cấp một lời giải thích tốt cho CRTP
một ví dụ mã?
Câu trả lời:
Nói tóm lại, CRTP là khi một lớp A
có một lớp cơ sở là một chuyên môn mẫu cho A
chính lớp đó. Ví dụ
template <class T>
class X{...};
class A : public X<A> {...};
Đó là tò mò định kỳ, phải không? :)
Bây giờ, những gì nó cung cấp cho bạn? Điều này thực sự mang lại cho X
mẫu khả năng trở thành một lớp cơ sở cho các chuyên ngành của nó.
Ví dụ: bạn có thể tạo một lớp singleton chung (phiên bản đơn giản hóa) như thế này
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;
Bây giờ, để biến một lớp tùy ý A
thành một singleton, bạn nên làm điều này
class A: public Singleton<A>
{
//Rest of functionality for class A
};
Bạn thấy đó? Mẫu singleton giả định rằng chuyên môn hóa của nó cho bất kỳ loại nào X
sẽ được kế thừa từ singleton<X>
đó và do đó sẽ có tất cả các thành viên (công khai, được bảo vệ) có thể truy cập được, bao gồm cả GetInstance
! Có những cách sử dụng hữu ích khác của CRTP. Ví dụ: nếu bạn muốn đếm tất cả các cá thể hiện đang tồn tại cho lớp của bạn, nhưng muốn gói gọn logic này trong một mẫu riêng (ý tưởng cho một lớp cụ thể khá đơn giản - có một biến tĩnh, tăng dần trong các hàm, giảm dần trong các hàm ). Hãy cố gắng làm nó như một bài tập!
Một ví dụ hữu ích khác, đối với Boost (Tôi không chắc họ đã triển khai nó như thế nào, nhưng CRTP cũng sẽ làm như vậy). Hãy tưởng tượng bạn muốn chỉ cung cấp toán tử <
cho các lớp của mình nhưng tự động vận hành ==
cho chúng!
bạn có thể làm như thế này:
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 <
}
Bây giờ bạn có thể sử dụng nó như thế này
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
Bây giờ, bạn đã không cung cấp toán tử rõ ràng ==
cho Apple
? Nhưng bạn có nó! Bạn có thể viết
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
Điều này có vẻ như bạn sẽ viết ít hơn nếu bạn chỉ viết toán tử ==
cho Apple
, nhưng hãy tưởng tượng rằng Equality
mẫu sẽ cung cấp không chỉ , ==
mà , v.v. Và bạn có thể sử dụng các định nghĩa này cho nhiều lớp, sử dụng lại mã!>
>=
<=
CRTP là một điều tuyệt vời :) HTH
Ở đây bạn có thể thấy một ví dụ tuyệt vời. Nếu bạn sử dụng phương thức ảo, chương trình sẽ biết những gì thực thi trong thời gian chạy. Việc thực hiện CRTP trình biên dịch sẽ quyết định thời gian biên dịch !!! Đây là một màn trình diễn tuyệt vời!
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;
? Mặc dù công bằng, kỹ thuật này có vẻ siêu hữu ích khi write
thực hiện các công việc khác.
CRTP là một kỹ thuật để thực hiện đa hình thời gian biên dịch. Đây là một ví dụ rất đơn giản. Trong ví dụ dưới đây, ProcessFoo()
đang làm việc với Base
giao diện lớp và Base::Foo
gọi foo()
phương thức của đối tượng dẫn xuất , đây là điều bạn nhắm đến để làm với các phương thức ảo.
http://coliru.stacked-crooking.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;
}
Đầu ra:
derived foo
AnotherDerived foo
foo()
được thực hiện bởi lớp dẫn xuất.
ProcessFoo()
hàm.
void ProcessFoo(T* b)
và không có Derogen và AnotherDeriving thực sự có nguồn gốc, nó vẫn hoạt động. IMHO sẽ thú vị hơn nếu ProcessFoo không sử dụng các mẫu nào đó.
ProcessFoo()
sẽ hoạt động với bất kỳ loại nào thực hiện giao diện, tức là trong trường hợp này, kiểu đầu vào T nên có một phương thức được gọi foo()
. Thứ hai, để làm cho một ứng dụng không có templatized ProcessFoo
hoạt động với nhiều loại, bạn có thể sẽ sử dụng RTTI, đây là điều chúng tôi muốn tránh. Hơn nữa, phiên bản templatized cung cấp cho bạn kiểm tra thời gian biên dịch trên giao diện.
Đây không phải là một câu trả lời trực tiếp, mà là một ví dụ về cách CRTP có thể hữu ích.
Một ví dụ cụ thể về CRTP là std::enable_shared_from_this
từ C ++ 11:
Một lớp
T
có thể kế thừa từenable_shared_from_this<T>
để kế thừa cácshared_from_this
hàm thành viên có được mộtshared_ptr
thể hiện trỏ tới*this
.
Đó là, kế thừa từ std::enable_shared_from_this
làm cho nó có thể đưa một con trỏ được chia sẻ (hoặc yếu) vào thể hiện của bạn mà không cần truy cập vào nó (ví dụ từ một hàm thành viên mà bạn chỉ biết về *this
).
Nó hữu ích khi bạn cần cung cấp std::shared_ptr
nhưng bạn chỉ có quyền truy cập vào *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));
}
};
Lý do bạn không thể vượt qua this
trực tiếp thay vì shared_from_this()
nó sẽ phá vỡ cơ chế sở hữu:
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);
Cũng như ghi chú:
CRTP có thể được sử dụng để thực hiện đa hình tĩnh (giống như đa hình động nhưng không có bảng con trỏ hàm ảo).
#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;
}
Đầu ra sẽ là:
Derived1 method
Derived2 method
vtable
s mà không sử dụng CRTP. Những gì vtable
thực sự cung cấp là sử dụng lớp cơ sở (con trỏ hoặc tham chiếu) để gọi các phương thức dẫn xuất. Bạn nên chỉ ra cách nó được thực hiện với CRTP tại đây.
Base<>::method ()
thậm chí không được gọi, bạn cũng không sử dụng đa hình ở bất cứ đâu.
methodImpl
tên method
của Base
và trong các lớp dẫn xuất methodImpl
thay vìmethod