CRTP
全称是curiously recurring template pattern。
在搞明白什么已经为什么需要CRTP之前,我们先假定一个场景: 我们想要用很多基于某个基类的衍生类,存入一个容器中,然后让这些衍生类做一个深拷贝,存入另一个容器中。这是比较常见的一种实际开发场景。
比如这种:
struct shape
{
virtual ~shape() = default;
};
struct circle:public shape
{
int radius = 1;
};
struct rectangle:public shape
{
int width = 1;
int length = 2;
};
我们在函数中可能会这样写:
int main()
{
vector<unique_ptr<shape>> source;
vector<unique_ptr<shape>> target;
source.emplace_back(make_unique<circle>());
source.emplace_back(make_unique<rectangle>());
for (auto&& p : source)
{
target.emplace_back(make_unique<shape>(*p));
}
return 0;
}
事实上,运行能通过,但是,如果我们改成这样:
using namespace std;
struct shape
{
virtual void message()
{
cout << "this is the base struct" << endl;
};
virtual ~shape() = default;
};
struct circle:public shape
{
void message() final
{
cout << "this is a circle" << endl;
}
circle(int i) :radius(i) {};
int radius ;
};
struct rectangle:public shape
{
void message() final
{
cout << "this is a rectangle" << endl;
}
rectangle(int w, int l) :width(w), length(l)
{};
int width = 1;
int length = 2;
};
int main()
{
shape* s=new rectangle(1, 2);
vector<unique_ptr<shape>> source;
vector<unique_ptr<shape>> target;
source.emplace_back(make_unique<circle>(1));
source.emplace_back(make_unique<rectangle>(1,2));
for (auto&& p : source)
{
p->message();
target.emplace_back(make_unique<shape>(*p));
}
for (auto&& p : target)
{
p->message();
}
return 0;
}
结果是:
this is a circle
this is a rectangle
this is the base struct
this is the base struct
然后我们发现其实子类根本没有实现对应的拷贝构造,因为我们调用make_unique<shape>实际上是在构造一个shape的类型,它本身并不会根据我们传入的类型不同而选择合适的派生类。最简单的解决方法如下:
using namespace std;
struct shape
{
virtual shape* clone() = 0;
virtual void message()
{
cout << "this is the base struct" << endl;
};
virtual ~shape() = default;
};
struct circle:public shape
{
shape* clone() final
{
return new circle(*this);
}
void message() final
{
cout << "this is a circle" << endl;
}
circle(int i) :radius(i) {};
int radius ;
};
struct rectangle:public shape
{
shape* clone() final
{
return new rectangle(*this);
}
void message() final
{
cout << "this is a rectangle" << endl;
}
rectangle(int w, int l) :width(w), length(l)
{};
int width = 1;
int length = 2;
};
int main()
{
vector<unique_ptr<shape>> source;
vector<unique_ptr<shape>> target;
source.emplace_back(make_unique<circle>(1));
source.emplace_back(make_unique<rectangle>(1,2));
for (auto&& p : source)
{
p->message();
target.emplace_back(unique_ptr<shape>(p->clone()));
}
for (auto&& p : target)
{
p->message();
}
return 0;
}
输出的结果为:
this is a circle
this is a rectangle
this is a circle
this is a rectangle
然后如果这样的类型成千上万,明显不能够手撸,我们学过泛型编程,那么很容易想到这个方法:
struct shape
{
template<typename T>
T* clone()
{
return new T(*this);
};
virtual void message()
{
cout << "this is the base struct" << endl;
};
virtual ~shape() = default;
};
那么问题来了,在emplace_back的时候不知道选择什么类型:
int main()
{
vector<unique_ptr<shape>> source;
vector<unique_ptr<shape>> target;
source.emplace_back(make_unique<circle>(1));
source.emplace_back(make_unique<rectangle>(1,2));
for (auto&& p : source)
{
p->message();
target.emplace_back(unique_ptr<shape>(p->clone<>())); //???
}
for (auto&& p : target)
{
p->message();
}
return 0;
}
那么好,有人说可以选择shape,我们试试:
target.emplace_back(unique_ptr<shape>(p->clone<shape>())); //shape
输出如下,如我们所见,这根本行不通,跟我们最开始错误一样,这种克隆方式会导致信息的丢失。
this is a circle
this is a rectangle
this is the base struct
this is the base struct
所以我们需要对T的做进一步的限制,也就是在传入的时候让它自动推断我们的T是什么,而不是用手撸的方式。
CRTP实际上就解决了这个问题,一起来学习一下:
using namespace std;
struct shape
{
virtual shape* clone() = 0;
virtual void message()
{
cout << "this is the base struct" << endl;
};
virtual ~shape() = default;
};
template<typename T>
struct shapeCRTP:public shape
{
shape* clone() final
{
return new T(*static_cast<T*>(this));
};
};
struct circle:public shapeCRTP<circle>
{
void message() final
{
cout << "this is a circle" << endl;
}
circle(int i) :radius(i) {};
int radius ;
};
struct rectangle :public shapeCRTP<rectangle>
{
void message() final
{
cout << "this is a rectangle" << endl;
}
rectangle(int w, int l) :width(w), length(l)
{};
int width = 1;
int length = 2;
};
有几点要注意:
- 中间多了一个shapeCRTP类,派生类继承的是它而不是shape,
- 注意T(*static_cast<T*>(this));,需要转换为T类型,因为this本身是shapeCRTP,我们希望生成的子类是circle或者rectangle,shapeCRTP的作用仅仅是帮助我们针对clone这个函数自动绑定子类的类型信息。
- 在完成后不需要再指定clone的类型,因为会自动调用shapeCRTP中的clone函数,而T根据circle以及rectangle的不同已经分别指定了其类型
int main()
{
vector<unique_ptr<shape>> source;
vector<unique_ptr<shape>> target;
source.emplace_back(make_unique<circle>(1));
source.emplace_back(make_unique<rectangle>(1,2));
for (auto&& p : source)
{
p->message();
target.emplace_back(unique_ptr<shape>(p->clone())); //???
}
for (auto&& p : target)
{
p->message();
}
return 0;
}
输出的结果为:
this is a circle
this is a rectangle
this is a circle
this is a rectangle
行,摸了