我是学习现代 C++ 编程的新手。我读过很多博客和问题,但没有找到这个问题的答案。在C++中,相比于final或者非虚函数,CRTP有什么优势?
当人们谈论 CRTP 时,经常会提到这样的一些代码作为示例:
template<typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
void foo() { ... }
};
class Derived : public Base<Derived> {
public:
void implementation() { ... }
};
int main() {
Derived d;
d.interface();
d.foo();
}
类
d
的对象Derived
可以使用foo
中的函数Base
,并且Derived
重写函数interface
。他们不搜索 vtable,因为这些不是虚函数。
但是我认为,当我使用inherit时,我可以达到同样的效果。
class Base1 {
public:
virtual void interface() = 0;
void foo() { ... }
};
class Derived1 : public Base1 {
public:
void interface() final { ... }
};
int main() {
Derived1 *d1 = new Derived1();
d1->interface();
d1->foo();
}
在那些代码中,当
d1
调用函数interface()
时,虽然该函数是虚函数,但编译器可以识别出它实际上是类Derived1
的函数,因为我标记了final
。并且 d1
也可以从 foo
调用函数 Base1
而无需搜索 vtable,因为 foo
不是虚函数。
但是在本例中,我并没有使用多态性。但
Base
是一个模板类。是Base<Derived>
。我们实际上已经知道它是Derived
,并且我们不能使用公共指针Base*
同时指向Base<A>
和Base<B>
。我认为将它与继承中的Base*
进行比较是不公平的。它应该与继承中的Derived
进行比较。我的意思是,当我们可以获得类型 Base<Derived>
时,我们也可以获得类型 Derived*
。
那么,在我看来,这两种重写和共享公共代码的方式的区别只是继承会在派生类的头部添加一个vtable。所以这两种方式可能会因为内存大小的不同,在内存使用和运行速度上会有一些差异。这些内存用来帮助我们通过父类指针指向子类,并在需要的时候进行虚函数调用。如果代码中没有这种情况,我们选择CRTP,否则继承。
我的想法正确吗? CRTP 还能做哪些继承不能做的事情?
正如评论中提到的,您的比较不太公平,因为您实际上并没有利用多态性。编译时多态性和运行时多态性都不是。一旦你这样做了,差异就会变得明显。
虚拟调度,运行时多态性:
void foo(Base1& b) {
b.interface();
}
只有在运行时才决定实际调用什么方法。这可以防止优化器内联调用。该方法是
final
中的 Derived
在这里没有帮助,因为 foo
使用接口而不是具体类型。
现在将其与 CRTP 进行比较,编译时多态性
template <typename T>
void foo(Base<T>& b) {
b.interface();
}
在
foo
的任何实例中,毫无疑问调用的是哪个函数。这具有一定的积极意义。例如,可以内联调用。