Cpp template about CRPT

some tips about the curiously recurring template pattern (CRTP) in cpp. The example comes from this video.

Naive version

Assume there is general class Shape, and then there is Square and Rectangle, which is derived class. The code looks like this

struct Shape {
virtual ~Shape() = default;
};

struct Square : public Shape {
// when using new to create the object
// the value in parathesis can be the initilizer
// for example
// int* p = new int(7); means dynamically allocated int with value 7
// the value of the int is default if we use the new int()
Square* Clone() { return new Square(*this); }
int x = 1;
};

struct Rectangle : public Shape {
Rectangle* Clone() { return new Rectangle(*this); }
int x = 1;
int y = 2;
};

int main() {
Square s1;
// the only difference of the clone function is the type
Square* s3 = s1.Clone();
return 0;
}

The issue of the code is that (1) the Clone function looks same for two subclass and it looks redoundant. Besides, (2) if there is a pointer to Shape, it can not call the Clone function, since it does not know what is the concrete type and the Clone function is not in the Base class.

Polymorphic version

One common strategy is to use the pure virtual function in the base class and then implement concrete functions in subsequent class.

#include <iostream>
#include <vector>
struct Shape {
virtual Shape* Clone() = 0;
virtual ~Shape() = default;
};

struct Square : public Shape {
// when using new to create the object
// the value in parathesis can be the initilizer
// for example
// int* p = new int(7); means dynamically allocated int with value 7
// the value of the int is default if we use the new int()
virtual Square* Clone() override {
std::cout << "clone Square" << std::endl;
return new Square(*this);
}
int x = 1;
};

struct Rectangle : public Shape {
virtual Rectangle* Clone() override {
std::cout << "clone Rectangle" << std::endl;
return new Rectangle(*this);
}
int x = 1;
int y = 2;
};

int main() {
// the only difference of the clone function is the type
// although we can use the virtual function
// there are still two clones implementation
Square s1;
Square* s2 = s1.Clone();

Rectangle r1;
Rectangle* r2 = r1.Clone();

std::vector<Shape*> shapelist;
shapelist.push_back(s2);
shapelist.push_back(r2);

for(auto s:shapelist){
Shape* tempShape = s->Clone();
}
return 0;
}

This code is a typical way to use the polymorphism.

CRTP version

If we want to move one step further and try to save the code in Clone function, we may try to do sth like this:

#include <iostream>
#include <vector>

struct Shape {
template <typename T>
T* Clone() {
std::cout << "clone Shape" << std::endl;
return new T(*this);
};
virtual ~Shape() = default;
};

struct Square : public Shape {
int x = 1;
};

struct Rectangle : public Shape {
int x = 1;
int y = 2;
};

int main() {
// the only difference of the clone function is the type
// although we can use the virtual function
// there are still two clones implementation
Square s1;

//Shape* s2 = s1.Clone<Square>(); /* error here: no matching constructor for initialization of 'Square' */
Shape* s2 = s1.Clone<Shape>();

Rectangle r1;
Shape* r2 = r1.Clone<Shape>();

std::vector<Shape*> shapelist;
shapelist.push_back(s2);
shapelist.push_back(r2);

for (auto s : shapelist) {
Shape* tempShape = s->Clone<Shape>();
}

return 0;
}

In this code, we use the template function in the parent class. But the issue is, the parent class do not know the information about the subclass, it can not call the correct clone function implemented by derived class.

That is how curiously recurring template pattern works here, it use another struct as the intermediate layer to help find the correct version of the class.

#include <iostream>
#include <vector>

struct Shape {
virtual Shape* Clone() = 0;
virtual ~Shape() = default;
};

//the middle layer class that knows the information of the dereived class
template <typename T>
struct ShapeCRTP : public Shape {
virtual Shape* Clone() override {
std::cout << "clone ShapeCRTP " << typeid(T).name() << std::endl;
return new T(*static_cast<T*>(this));
};
};

struct Square : public ShapeCRTP<Square> {
int x = 1;
};

struct Rectangle : public ShapeCRTP<Rectangle> {
int x = 1;
int y = 2;
};

int main() {
// the only difference of the clone function is the type
// although we can use the virtual function
// there are still two clones implementation
Square s1;
Shape* s2 = s1.Clone();

Rectangle r1;
Shape* r2 = r1.Clone();

std::vector<Shape*> shapelist;
shapelist.push_back(s2);
shapelist.push_back(r2);

for (auto s : shapelist) {
Shape* tempShape = s->Clone();
}

return 0;
}

In this code, the concrete class inheriet from the ShapeCRTP and also provide their own class information during the declaration. When call the clone function, it does not need to add the template name signature, since the related information is set to the ShapeCRTP during the initilization of the class. By this way, we achieve the goal: (1) do not contains the repeated function or similar implementation in the child class (2) support the polymorphic and use the parent class to manage all the derived class.

推荐文章