我的一个 friend 问我“如何使用 CRTP 替换多级继承中的多态性”。更准确地说,在这样的情况下:
struct A {
void bar() {
// do something and then call foo (possibly) in the derived class:
foo();
}
// possibly non pure virtual
virtual void foo() const = 0;
}
struct B : A {
void foo() const override { /* do something */ }
}
struct C : B {
// possibly absent to not override B::foo().
void foo() const final { /* do something else */ }
}
我和我的 friend 都知道 CRTP 不是多态性的直接替代品,但我们对可以同时使用这两种模式的情况感兴趣。 (为了这个问题,我们对每种模式的优缺点不感兴趣。)
最佳答案
(1) 层次结构中最顶层的类如下所示:
template <typename T>
class A {
public:
void bar() const {
// do something and then call foo (possibly) in the derived class:
foo();
}
void foo() const {
static_cast<const T*>(this)->foo();
}
protected:
~A() = default;
// Constructors should be protected as well.
};
A<T>::foo()
其行为类似于纯虚方法,因为它没有“默认实现”并且调用被定向到派生类。然而,这并不妨碍 A<T>
从被实例化为非基类。要获得此行为 A<T>::~A()
制作 protected
.
备注:不幸的是 GCC bug在 = default;
时公开特殊成员函数用来。在这种情况下,应该使用
protected:
~A() {}
不过,对于对构造函数的调用与对析构函数的调用不匹配的情况(这可能通过 operator new
发生),保护析构函数是不够的。因此,建议同时保护所有构造函数(包括复制构造函数和移动构造函数)。
当 A<T>
的实例化时应该被允许和A<T>::foo()
应该表现得像一个非纯虚方法,然后 A
应该类似于模板类B
下面。
(2) 层次结构中间的类(或最上面的类,如上一段所述)如下所示:
template <typename T = void>
class B : public A<B<T>> { // no inherinace if this is the topmost class
public:
// Constructors and destructor
// boilerplate code :-(
void foo() const {
foo_impl(std::is_same<T, void>{});
}
private:
void foo_impl(std::true_type) const {
std::cout << "B::foo()\n";
}
// boilerplate code :-(
void foo_impl(std::false_type) const {
if (&B::foo == &T::foo)
foo_impl(std::true_type{});
else
static_cast<const T*>(this)->foo();
}
};
构造函数和析构函数是公共(public)的,T
默认为 void
.这允许 B<>
类型的对象成为层次结构中最派生的并使其合法:
B<> b;
b.foo();
另请注意B<T>::foo()
在某种意义上表现为非纯虚方法,如果 B<T>
是派生度最高的类(或者,更准确地说,如果 T
是 void
),那么 b.foo();
调用“默认实现 foo()
”(输出 B::foo()
)。如果 T
不是 void
,然后调用被定向到派生类。这是通过标签调度完成的。
测试&B::foo == &T::foo
需要避免无限递归调用。确实,如果是派生类,T
, 不重新实现 foo()
, 来电static_cast<const T*>(this)->foo();
将解析为 B::foo()
调用 B::foo_impl(std::false_type)
再次。此外,这个测试可以在编译时解决,代码是 if (true)
或 if (false)
并且优化器可以完全删除测试(例如带有 -O3 的 GCC)。
(3) 最后,层次结构的底部看起来像:
class C : public B<C> {
public:
void foo() const {
std::cout << "C::foo()\n";
}
};
或者,可以删除 C::foo()
如果继承的实现 ( B<C>::foo()
) 就足够了。
请注意 C::foo()
类似于 final 方法,调用它不会将调用重定向到派生类(如果有)。 (为了使它不是最终的,应该使用像 B
这样的模板类。)
(4)另见:
关于c++ - CRTP 和多级继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18174441/