打印
[LOOK]

李老师群课之元编程

[复制链接]
1880|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

主讲 李老师(John Lee
Meta Programming真是个可怕的东西,把觉得不可能实现的东西实现了。
编程就是编写计算机程序来编写或操作其它程序(或它自己)作为它们的数据,或者是在编译时而不是运行时干一部分工作。大多数情况下,在同一时间里程序员使用元编程比手写代码更有效率。
元编程非常奇妙。普通的编程,处理的是数据,而类型在编程时就定死了。元编程的处理对象,则是类型。很多时候,编程时定义某数据的类型,不一定是最佳,比如数组。
在普通的程序设计里,你必须给数组一个无条件的、明确的类型,比如uint16_t等等但最终你可能发现,实际运行时,数组的每个数据都不超过uint8_t。以致于不同的类型要写类似的代码。也可能浪费了一半的存储。如果你很在乎节省那些存储,你可以在编完程序后,回头来把数组类型改成uint8_t
做个宏定义 ? 做个条件编译的宏?宏定义也是要“定义的”。
很多时候,数组的大小,不可能简单地知道。特别是这个数组的size,需要根据分布在各个模块中的其它数据或参数,综合计算得出。宏来做这件事,几乎无法达成。
然后应该怎么做?然后就是,元编程可以轻松的做到。
元数据怎么做?重点在于struct/class中的常量。
struct中可以定义静态常量和类型,这个是关键:
struct A {
    static const bool value = .....
};
value可以不依赖A的对象,而只需要对A类访问就可以A::value。
类中的常量,是编译器记住常量值,而不需要实际的开销。
类中还可以定义类型,这个是元编程的重要支持。
我们来看一个元编程的if else。
C++系统头文件中,有一个名为type_traits的文件里面有一个模板:conditional
前置定义是:
template<bool, typename, typename>
struct conditional;
根据说明,conditional模板是根据第1个参数bool的值(truefalse),来确定conditional中的type的类型。
如果bool true,则type为第2个参数的类型,如果为false,则type为第3个参数的类型。
我们先说使用,比如,我们需要根据一个bool常量来定义一个数组,如果常量值为true,我们定义这个数组为uint8_t,否则定义为uint16_t
typename std::conditional<COND, uint8_t, uint16_t>::type array[20];
这个就是定义array数组。cond为真,则是uint8_t array[20]
实际使用远比这个复杂,太简单的情况也用不着mixin crtp等等。
这里要理解conditionaltype,是怎么跟随bool参数变化的。
template<bool _Cond, typename _Iftrue, typename _Iffalse>
    struct conditional
    { typedef _Iftrue type; };

  // Partial specialization for false.
  template<typename _Iftrue, typename _Iffalse>
    struct conditional<false, _Iftrue, _Iffalse>
    { typedef _Iffalse type; };
就这么几行。这里定义了两个conditional类模板。
1个是普通的
template<bool _Cond, typename _Iftrue, typename _Iffalse>
struct conditional
{ typedef _Iftrue type; };
1个在struct里直接定义了 type_Iftrue。如果没有第2conditional,那么我们永远只能得到一个固定的类型。
2conditional类模板定义,术语称为partial specialization,一般翻译为偏特化,就是部分特别处理。
  // Partial specialization for false.
template<typename _Iftrue, typename _Iffalse>
struct conditional<false, _Iftrue, _Iffalse>
{ typedef _Iffalse type; };
这个偏特化的类模板,固定了第1个参数boolfalse,其余两个参数不变。
编译器就知道,如果程序里使用了conditional类模板,并且第1个参数为false的,就去匹配那个偏特化的版本。在这个版本的struct 里,定义了 type 类型为 _Iffalse,就是第3个参数的类型。
回到最开始的话题。C++可以根据一些条件(可以说就是类模板参数),来定义某类型。这离不开偏特化或全特化。
实际上,你看到的不同的类型,是分别属于不同特化版本的类模板。然而,对程序的使用来说,可以认为是透明的。就比如conditional类模板,如果你不去看type_traits文件,那么你可能根本不知道它具体是怎么实现的,你可能认为它就是一个类模板。
元编程就是通过提供了各种特化的类模板,每个模板中都有一个结果,来让程序不断地匹配,从而得到想要的结果。就是让编译器自己去选择合适的模板。这就是编译时计算。
你必须自己提供所有的结果,和达成这些结果的条件,编译器就会根据条件找到合适的结果。但是,程序中遇到数据,不只是常量。对于变量,模板不是很好用,需要一些辅助手段,把变量变换为常量,才能交给模板计算。
void foo(bool b)     // b是输入的变量
{
if (b)        // 通过实际的语句判断变量值
// b 确定为 true,下面直接使用常量的true计算
conditional<true, .........
else                      // b 确定为 false, 下面直接使用常量的false计算
        conditional<false, .......
}
类模板,通过给出的不同的模板参数,实例化成不同的类。元编程对变量不是很适用。处理常量非常好。但没有要求你在程序中只用元编程啊,
程序中可以把元编程和普通编程混合在一起。对常量就用元编程,对变量就用普通编程。一个程序如果全部是元编程的话,那么这个程序里没有变量。这种在实际应用中,基本没有。
元编程主要是为了解决程序中的常量运算,包括生成各种常量数据结构,也是非常合适的。最能说明问题的,是USB的配置描述符。我看目前好像都是人工在写描述符数据,使用元编程就可以自动生成。用户只要在类模板实例化时,提供一些“必要”的参数,像版本号,VIDPID等等。而描述符中的其它可计算数据,则完全可以由元编程来完成。比如描述符长度,个数等等,还有描述符中的各个ID
其实,在描述符中,“必须”要用户给出的数据不多。而那些长度,idinterface从属关系,interface类别数据,endpoint 属性和方向等等,却比较繁杂,人工计算很容易出错。
看看这个例子,里面有多少描述符的影子:
在这里,你可以看到定义描述符所必须要用户给出的数据。这些数据是跑不掉的。
除此之外的其它数据,都通过元编程计算得出,并和用户给出的必要数据组合成了描述符。
描述符数据保存在类模板里,所以Get String Descriptor请求,都不必用户处理了,内部直接操作报告给host
Get Descriptor( Device, Configuration, String)
虽然简洁,但对于不习惯元编程的人来说,看起来确实有些晕。
要把用户逻辑尽量减少,在框架内包含尽量多的逻辑。但实例化后,还要尽量把必须的逻辑包含进程序,用不到的逻辑,尽量排除,还要保持最大的灵活性。
这个框架的灵活之处在于:
     
这里可以加入多个任意的 interface这个的 interface hid,我可以把 msc 的定义 interface audio 定义的interface 都加在这里,这个 usb 设备就是一个复合设备了,同时具有 hid, msc, audio 功能。这个才是 mixin 的真正体现。

相关帖子

沙发
mahui843|  楼主 | 2013-7-2 13:19 | 只看该作者
先抢沙发!

使用特权

评论回复
板凳
gaoyang9992006| | 2013-7-2 15:56 | 只看该作者
我是来学习的

使用特权

评论回复
地板
gaoyang9992006| | 2013-7-2 15:56 | 只看该作者
我是来学习的

使用特权

评论回复
5
X-Hawk| | 2013-7-4 02:05 | 只看该作者

使用特权

评论回复
6
缥缈九哥| | 2013-7-4 12:49 | 只看该作者
还是不懂。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:境有界  思无域

6

主题

63

帖子

1

粉丝