-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
使用 CRTP 实现编译期接口定义 #11780
Comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
使用 CRTP 实现编译期接口定义
https://ift.tt/mJcnloY
前言
有一种类型叫
pure virtual
,不仅多态,而且强制要求Base
类不做实现(子类必须 override),只声明为接口,实现了接口语义但有时候并不需要多余的多态,我就需要一个类做接口声明
C++
作为追求 zero overhead 的语言,能不能做到完全不必承担virtual
开销的接口语义?那当然可以,只要你能折腾
PS. 全文废话非常多(如果不愿意看这个东西是怎么来的),解决方案只在正文最后一段
期望
期望自然是做到接口语义,伪代码如下
初探
既然是编译时,又是继承,还是接口,很容易想到用
CRTP
惯用法为了简单起见,目前接口只有
void func()
然后随便写一个测试
直观上来说,由于
Impl
继承了奇异递归的Interface
,而
Interface
声明的__require
中调用了子类Impl
的func
,目前显然是没有
func
符号,编译期就会无法通过,起到接口约束的作用要不编译试试?
坑 1:模板
很不幸,这段代码是完全可以编译的,这个所谓的
CRTP
完全没有实现接口语义的作用那是因为踩了__模板实例化__的坑:你不用,编译期间就不会实例化,哪怕你调用了
Interface<Impl>
,由于没有任何一处调用到__require()
,那么__require()
部分就不会实例化也就是说,你需要在每一个子类中显式地调用到
__require()
才能起到约束作用开放权限不是大事,但是每一个子类都要加一个极具侵入性的函数是非常丑陋的行为
能不能再改进一点?我不希望子类有任何额外的添加
构造即检查
其实思路很简单,把检查的流程写到接口类的构造函数即可
实现类一下子清爽多了!而且做到了接口约束的效果
坑 2:副作用
上面这段代码忽略的一个很大问题是调用
that->func()
是有__副作用__的怎么解决?
最简单的方法是效仿
linux kernel
里的小技巧:sizeof
是编译阶段的求值,并不会真的执行但是仍有不足之处:
sizeof(void)
并不是特别合法的行为既然如此那就用稍微
modern C++
的做法:decltype
这里的意思是说
nullptr
decltype
同样只是编译时推导类型虽然代码有点令人不适,但它确实是类型安全的
但这代码确实是令人不适,那我们还能换种形式
那么,这样就能结束了?一个完美的接口类型?
并没有
坑 3:参数类型
假设现在要求实现另一个接口
它现在有两个问题
decltype
中表达出int
和double
参数int
先来解决 2:使用
std::is_same<>
再一次判断再来解决 1:反正是编译时推导的东西,可以通过默认构造函数解决,能塞进去就算成功
总结如下
似乎又解决了一个难题?还没完! 假设现在要求实现另外一个接口
前面那种基于默认构造函数的做法是无效的
这不是只要传入一个
NoDefaultCtor(1)
就能解决的问题它导致的更严重的后果是:无法使用
template
来封装整个静态检查过程一个类被限制了只能用某一种特定的构造函数的情况,是非常难通过模板来推导的
模板参数推导
既然类型是个坎,那就得正面从类型去考虑,不要停留在值或者对象的角度
对类型最有表达能力的方式自然是模板的推导
已知:给出函数,通过推导可以得出它的返回类型和参数
怎么做的:见 sRpc/FunctionTraits.h at master · Caturra000/sRpc (github.com)
我在上述链接实现了一个简单的
FunctionTraits
,可以推导出各种不同的函数(普通函数、成员函数、lambda
、std::function
)的元信息另一方面:希望通过注册的形式来完成接口检查,由使用方主动告知函数需要的信息,然后对比一下是否匹配(
IsSameInterface
)即可从这里我们已经到了使用模板的方案,除了显得繁琐
但这个可以用宏来缓解一下
__require
流程,毕竟过程是很单调重复的(详略)现在,前面的问题全部都解决了,通过当前的模板甚至能扩展到处理左右值、
const
语义等细节,已经很能打了坑 4:死于重载
在上述模板方案中,有一点是模板无法解决的(或者很难处理)
对于
FunctionTraits<decltype(&Interface::_func3)>
这种形式的调用,模板无法区分_func3
到底是什么比如要求实现含有重载的接口
这时候就无法解析了,很显然,因为存在二义性
用重载解决重载
这里提供一种新的处理思路:
如果给出成员函数指针
ReturnType Class::*fptr
只要有对应的函数参数列表,那么
fptr
都是能匹配上的相比上述的解决方案,这里能匹配的原因是多提供了参数的信息,这是基本语法就提供的特性,不需要额外的模板推导
因此换一种方式:不是从函数推导出参数,而是提供参数和利用上述重载特性找出匹配函数
这里重新定义一套
require-define
的步骤,如下所示是不是非常简洁明了?只要
define
匹配上的,那么接口就是合法同样的,它处理了前面所有的问题
THE END
至此,总算是折腾出了一套 CRTP 下的静态接口,总结有以下优点:
virtual
开销,完全编译时处理via Caturra’s Blog
September 10, 2024 at 11:29AM
The text was updated successfully, but these errors were encountered: