CRTP.试着理解一个给定的例子
CRTP. Trying to understand an given example
当我试图理解CRTP时,我遇到了这个例子,它对我来说有点模糊。如果我做一些更简单的事情,我可以获得同样的结果:
#pragma once
#include <iostream>
template <typename T>
class Base
{
public:
void method() {
static_cast<T*>(this)->method();
}
};
class Derived1 // : public Base<Derived1> <-- commented inherintance
{
public:
void method() {
std::cout << "Derived1 method" << std::endl;
}
};
class Derived2 // : public Base<Derived2> <-- commmented inherintance
{
public:
void method() {
std::cout << "Derived2 method" << std::endl;
}
};
#include "crtp.h"
int main()
{
Derived1 d1;
Derived2 d2;
d1.method();
d2.method();
return 0;
}
我的问题是:CRTP在这里的目的是什么?经过思考,我想这个用途是为了允许这样的东西:
template<typename T>
void call(Base<T>& x)
{
x.method();
}
并称之为
int main()
{
Derived1 d1;
Derived2 d2;
call(d1);
call(d2)
}
我说得对吗?
经过思考,我想这个用途是为了允许这样的东西:
template<typename T> void call(Base<T>& x) { x.method(); }
并称之为
int main() { Derived1 d1; Derived2 d2; call(d1); call(d2); }
您是对的,这是使用所提供的CRTP示例的一种可能方式。
然而,必须注意的是,正如您已经注意到的那样,示例的main()
在给定的情况下显示了一个糟糕的用例(直接调用派生方法),因为我们不需要CRTP或继承方法来使示例工作。
关于此:
vtables真正提供的是使用基类(指针或引用)来调用派生方法。您应该在这里展示如何使用CRTP。
为了理解这一点,谨慎的做法是首先解释为什么我们想要多态性。在任何其他必须理解的东西之前,或者CRTP看起来只不过是一个巧妙的模板技巧。
你最初的猜测很准确——当你想访问派生类的行为/方法时,你会想要多态性,它覆盖(或定义)我们需要通过基类可用的行为。
在像这样的普通继承中:
struct A { void method() {} };
struct B : A { void method() {} };
int main () {
B b;
b.method();
return 0;
}
发生的情况是B::method()
被激发,是的,但如果我们省略了B::method()
,那么它实际上就是在调用A::method()
。在这个意义上,B::method()
覆盖A::method()
。
如果我们想调用B::method()
,但只有一个A
对象,这是不够的:
int main () {
B b;
A *a = &b;
a->method(); // calls A::method();
return 0;
}
实现这一点(至少)有两种方法:动态多态性或静态多态性。
动态多态性
struct A1 {
virtual ~A1(){}
virtual void method() {}
};
struct B1 : A1 {
virtual ~B1(){}
virtual void method() override {}
};
int main () {
B1 b;
A1 *a = &b;
a->method(); // calls B1::method() but this is not known until runtime.
return 0;
}
详细地说,由于A1
具有虚拟方法,因此存在用于该类的特殊表(vtable
),其中为每个虚拟方法存储函数指针。这个函数指针可以被派生类覆盖(在某种意义上),这就是B1
的每个实例所做的——设置为B1::method
。
因为这些信息存在于运行时,编译器不需要知道更多信息,只需要查找A1::method
的函数指针并调用它所指向的任何内容,无论是A1::method
还是B1::method
。相反,因为它是在运行时完成的,所以这个过程往往比事先知道类型要慢。。。
静态多态性
同样的例子以避免使用vtable的方式重新进行:
template < class T > struct A2 {
void method() {
T *derived_this = static_cast<T*>(this);
derived_this->method();
}
};
struct B2 : A2 < B2 > {
void method() {}
};
int main () {
B2 b;
A2<B2> *a = &b; // typically seen as a templated function argument
a->method(); // calls B2::method() statically, known at compile-time
return 0;
}
这一次我们不使用任何东西的动态查找,使用模板可以看到完全相同的行为,并根据您的示例函数template<class T> call(Base<T>*);
使用模板参数类型推导
当使用A*
类型执行从A::method
到B::method
的分辨率时,两种方法之间的主要区别是。CRTP允许编译器了解这个派生方法,因为我们使用模板从基类型交换到派生类型,因此静态多态性。vtable
与虚拟函数/方法一起使用,并通过存储指向正确类的方法的函数指针,间接地"执行交换"(这是错误的,但有助于这样想)。
@Etherealone向回答者(真正的单词?)提出的问题是演示我在上面刚刚展示的内容——如何使用CRTP来使用基类指针调用派生类方法,而不是依赖vtable
(一层间接实现这一点,而不知道调用代码中的派生类型本身)。
是的,模板函数"call"也可以做同样的工作。但CRTP有时可能会更好。用法,例如:
Base<Derived1> *d1 = new Derived1;
d1->method();
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 运行同一解决方案的另一个项目的项目
- 挂起和取消挂起一个文件DLL
- 用C++中的一个变量定义一个常量
- 函数向量_指针有不同的原型,我可以构建一个吗
- 在c++中用vector填充一个简单的动态数组
- 如何在选项卡视图Qt中设置一个新项目,并保存以前的项目
- 预处理器:插入结构名称中的前一个行号
- 我在c++代码中生成了一个运行时#3异常
- 我想将一个对T类型的非常量左值引用绑定到一个T类型的临时值
- 从链接列表c++中删除一个项目
- 告诉一个 const char 数组,除了编译时 C 样式的字符串外,它不以 '