在意识到我的原始答案实际上并没有处理手头的最终覆盖问题之后,我想我会添加它。我想以与我之前的答案类似的方式提出“最终覆盖”解决方案。
问题:
CRTP 接口类总是通过静态转换重定向到最高派生类。这与“final”函数的概念不一致:如果所需的“final”函数没有在最高派生类上实现,并且被更高的类“覆盖”(因为你不能给函数一个“final”属性,除非它是虚拟的,这是我们在 CRTP 中试图避免的),CRTP 接口将不会重定向到所需的“最终”函数,而是重定向到“覆盖”。
解决方案:
将界面拆分为三个概念:
- 没有任何重定向功能的抽象接口类,它继承:
- 一个抽象重定向类,其重定向函数重定向到最高派生类,除非一个或多个重定向函数被以下内容覆盖:
- 一个具体的“重定向覆盖”类,它用一个实现覆盖重定向功能。
在实例化具体实现类时,我们不是通过所有“可继承的实现类”将具体实现类作为模板参数传递给接口,而是将接口将从其继承的重定向类作为模板参数传递。
当我们想要将一个函数设为“final”时,我们只需创建一个“重定向覆盖类”,它继承自抽象重定向类,并覆盖我们想要设为 final 的重定向函数。然后我们将这个新的“重定向覆盖类”作为参数传递给所有可继承的实现类。
使用这种方法:
- “final”函数被直接调用而不是通过强制转换重定向(除非您需要在可继承的实现类而不是重定向覆盖类中实现“final”函数),
- “最终”功能不能被任何未来的用户代码覆盖,
- 每个“最终”函数只需要每个继承级别的额外 ImplFinal 类,没有额外的样板。
这听起来很复杂,所以这是我制作的流程图,试图让事情更容易理解:
DImpl 和 EImpl 具有最终函数,当 DImpl 或 EImpl 从以下位置继承时,这些函数不能被覆盖:
示例代码:
#include <iostream>
#include <type_traits>
template <class Top>
struct Redirect
{
protected:
// The "pure virtual functions"
inline void fooImpl()
{
top().fooImpl();
}
inline void fooImpl2()
{
top().fooImpl2();
}
inline void fooImpl3()
{
top().fooImpl3();
}
inline void fooImpl4()
{
top().fooImpl4();
}
inline Top& top()
{
// GCC doesn't allow static_cast<Top&>(*this)
// since Interface uses private inheritance
static_assert(std::is_base_of<Redirect, Top>::value, "Invalid Top class specified.");
return (Top&)(*this);
}
};
// Wraps R around the inner level of a template T, e.g:
// R := Redirect, T := X, then inject_type::type := Redirect<X>
// R := Redirect, T := A<B<C<X>>>, then inject_type::type := A<B<C<Redirect<X>>>>
template<template<class> class R, class T>
struct inject_type
{
using type = R<T>;
};
template<template<class> class R, class InnerFirst, class... InnerRest, template<class...> class Outer>
struct inject_type<R, Outer<InnerFirst, InnerRest...>>
{
using type = Outer<typename inject_type<R, InnerFirst>::type, InnerRest...>;
};
// We will be inheriting either Redirect<...> or something
// which derives from it (and overrides the functions).
// Use private inheritance, so that all polymorphic calls can
// only go through this class (which makes it impossible to
// subvert redirect overrides using future user code).
template <class V>
struct Interface : private inject_type<Redirect, V>::type
{
using impl = Interface;
void foo()
{
std::cout << "Calling Interface::foo()\n";
fooImpl();
}
void foo2()
{
std::cout << "Calling Interface::foo2()\n";
fooImpl2();
}
void foo3()
{
std::cout << "Calling Interface::foo3()\n";
fooImpl3();
}
void foo4()
{
std::cout << "Calling Interface::foo4()\n";
fooImpl4();
}
private:
using R = typename inject_type<::Redirect, V>::type;
protected:
using R::fooImpl;
using R::fooImpl2;
using R::fooImpl3;
using R::fooImpl4;
};
template<class V>
struct DefaultImpl : Interface<V>
{
template<class>
friend struct Redirect;
protected:
// Picking up typename impl from Interface, where all polymorphic calls must pass through
using impl = typename DefaultImpl::impl;
void fooImpl()
{
std::cout << "Default::fooImpl()\n";
}
void fooImpl2()
{
std::cout << "Default::fooImpl2()\n";
std::cout << "Calling foo() from interface\n";
impl::foo();
}
void fooImpl3()
{
std::cout << "Default::fooImpl3()\n";
std::cout << "Calling highest level fooImpl2() from interface\n";
impl::fooImpl2();
}
void fooImpl4()
{
std::cout << "Default::fooImpl4()\n";
std::cout << "Calling highest level fooImpl3() from interface\n";
impl::fooImpl3();
}
};
template<class V>
struct AImpl : public DefaultImpl<V>
{
template<class>
friend struct Redirect;
protected:
void fooImpl()
{
std::cout << "A::fooImpl()\n";
}
};
struct A : AImpl<A>
{
};
template<class V>
struct BImpl : public AImpl<V>
{
template<class>
friend struct Redirect;
protected:
BImpl()
: i{1}
{
}
private:
int i;
void fooImpl2()
{
std::cout << "B::fooImpl2(): " << i << "\n";
}
};
struct B : BImpl<B>
{
};
template<class V>
struct CImpl : public BImpl<V>
{
template<class>
friend struct Redirect;
protected:
CImpl(int x = 2)
: i{x}
{
}
private:
int i;
void fooImpl3()
{
std::cout << "C::fooImpl3(): " << i << "\n";
}
};
struct C : CImpl<C>
{
C(int i = 9)
: CImpl(i)
{
}
};
// Make D::fooImpl4 final
template<class V>
struct DImplFinal : public V
{
protected:
void fooImpl4()
{
std::cout << "DImplFinal::fooImpl4()\n";
}
};
// Wrapping V with DImplFinal overrides the redirecting functions
template<class V>
struct DImpl : CImpl<DImplFinal<V>>
{
};
struct D : DImpl<D>
{
};
template<class V>
struct EImpl : DImpl<V>
{
template<class>
friend struct Redirect;
protected:
void fooImpl()
{
std::cout << "E::fooImpl()\n";
}
void fooImpl3()
{
std::cout << "E::fooImpl3()\n";
}
// This will never be called, because fooImpl4 is final in DImpl
void fooImpl4()
{
std::cout << "E::fooImpl4(): this should never be printed\n";
}
};
struct E : EImpl<E>
{
};
// Make F::fooImpl3 final
template<class V, class Top>
struct FImplFinal : public V
{
protected:
// This is implemented in FImpl, so redirect
void fooImpl3()
{
top().fooImpl3();
}
// This will never be called, because fooImpl4 is final in DImpl
void fooImpl4()
{
std::cout << "FImplFinal::fooImpl4() this should never be printed\n";
}
inline Top& top()
{
// GCC won't do a static_cast directly :(
static_assert(std::is_base_of<FImplFinal, Top>::value, "Invalid Top class specified");
return (Top&)(*this);
}
};
// Wrapping V with FImplFinal overrides the redirecting functions, but only if they haven't been overridden already
template<class V>
struct FImpl : EImpl<FImplFinal<V, FImpl<V>>>
{
template<class>
friend struct Redirect;
template<class, class>
friend struct FImplFinal;
protected:
FImpl()
: i{99}
{
}
// Picking up typename impl from DefaultImpl
using impl = typename FImpl::impl;
private:
int i;
void fooImpl2()
{
std::cout << "F::fooImpl2()\n";
// This will only call DFinal::fooImpl4();
std::cout << "Calling fooImpl4() polymorphically. (Should not print FImplFinal::fooImpl4() or EImpl::fooImpl4())\n";
impl::fooImpl4();
}
void fooImpl3()
{
std::cout << "FImpl::fooImpl3(), i = " << i << '\n';
}
};
struct F : FImpl<F>
{
};
int main()
{
std::cout << "### A ###\n";
A a;
a.foo();
a.foo2();
a.foo3();
a.foo4();
std::cout << "### B ###\n";
B b;
b.foo();
b.foo2();
b.foo3();
b.foo4();
std::cout << "### C ###\n";
C c;
c.foo();
c.foo2();
c.foo3();
c.foo4();
std::cout << "### D ###\n";
D d;
d.foo();
d.foo2();
d.foo3();
d.foo4();
std::cout << "### E ###\n";
E e;
e.foo();
e.foo2();
e.foo3();
e.foo4();
std::cout << "### F ###\n";
F f;
f.foo();
f.foo2();
f.foo3();
f.foo4();
}
代码打印:
### A ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
Default::fooImpl2()
Calling foo() from interface
Calling CrtpInterface::foo()
A::fooImpl()
### B ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
Default::fooImpl3()
Calling highest level fooImpl2() from interface
B::fooImpl2(): 1
### C ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 9
Calling CrtpInterface::foo4()
Default::fooImpl4()
Calling highest level fooImpl3() from interface
C::fooImpl3(): 9
### D ###
Calling CrtpInterface::foo()
A::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
C::fooImpl3(): 2
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### E ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
B::fooImpl2(): 1
Calling CrtpInterface::foo3()
E::fooImpl3()
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()
### F ###
Calling CrtpInterface::foo()
E::fooImpl()
Calling CrtpInterface::foo2()
F::fooImpl2()
Attempting to call FFinal::fooImpl4() or E::fooImpl4()
DImplFinal::fooImpl4()
Calling CrtpInterface::foo3()
FImpl::fooImpl3(), i = 99
Calling CrtpInterface::foo4()
DImplFinal::fooImpl4()