前言
一个深度学习框架的初步实现为例,讨论如何在一个相对较大的项目中深入应用元编程,为系统优化提供更多的可能。
以下内容结合书中原文阅读最佳!!!
一、背景知识:支配与虚继承
支配(dominance)和虚继承(virtual inheritance)是面向对象编程中的两个概念,用于处理多重继承时的冲突和问题。
1. 支配:
支配是指在多重继承中,当一个类派生自多个基类,并且这些基类有相同的成员函数或数据成员时,派生类需要通过某种方式来选择使用哪个基类的成员。这个选择的过程称为支配。支配决定了在派生类中使用哪个基类的成员。
2. 虚继承:
虚继承是一种语言机制,用于解决多重继承中的菱形继承问题。菱形继承指的是当一个派生类同时继承了两个基类,而这两个基类又继承自同一个共同的基类。这样会导致派生类中有两个相同的基类子对象,从而引起二义性和冲突。
虚继承通过在继承关系中使用关键字"virtual"来限制被继承的基类,使得在继承体系中只保留一份共同的基类子对象。这样可以避免菱形继承的问题,消除二义性和冲突。
联系:
支配和虚继承在多重继承中都与处理类成员的冲突和二义性有关。支配通过在派生类中明确指定使用哪个基类的成员来解决问题,而虚继承则通过限制继承的基类来消除二义性和冲突。
当存在多个基类且有成员函数或数据成员重叠时,派生类可以使用支配来选择使用特定的基类成员。而当继承体系中出现钻石继承问题时,通过应用虚继承可以消除二义性和冲突,确保只有一份共同的基类子对象存在。
因此,支配和虚继承都是为了解决多重继承中的问题,在不同方面提供了不同的解决方案。支配是通过在派生类中进行选择,而虚继承是在继承关系中进行限制,以确保继承体系的一致性。
1.1 C++代码示例:
#include <iostream> class Base { public: Base() { std::cout << "Base constructor called." << std::endl; } virtual void foo() { std::cout << "Base foo() called." << std::endl; } }; class Derived1 : public virtual Base { public: Derived1() { std::cout << "Derived1 constructor called." << std::endl; } void foo() override { std::cout << "Derived1 foo() called." << std::endl; } }; class Derived2 : public virtual Base { public: Derived2() { std::cout << "Derived2 constructor called." << std::endl; } void foo() override { std::cout << "Derived2 foo() called." << std::endl; } }; class MultipleDerived : public Derived1, public Derived2 { public: MultipleDerived() { std::cout << "MultipleDerived constructor called." << std::endl; } void foo() override { std::cout << "MultipleDerived foo() called." << std::endl; } }; int main() { MultipleDerived obj; obj.foo(); return 0; }
代码讲解:
- 首先,当创建 MultipleDerived 类的对象时,会依次调用 Base、Derived1、Derived2 和 MultipleDerived 的构造函数。
输出结果为:
Base constructor called. Derived1 constructor called. Derived2 constructor called. MultipleDerived constructor called.
- 接下来,当调用 obj.foo() 时,会根据虚继承中的继承规则,选择一个有效的虚函数。根据最后一个派生类 MultipleDerived 中的虚函数 foo() 的实现,输出结果为:
MultipleDerived foo() called.
由于虚继承在这个示例中的使用,继承体系中只保留了一个共同基类 Base 的子对象,这样就解决了菱形继承带来的二义性和冲突。重要的是要注意,使用虚继承并不会改变继承体系的结构,而只是修改了派生类的构造方式和虚函数的选择规则。
二、policy对象与policy支配结构
struct AccPolicy { struct AccuTypeCate { struct Add; struct Mul; }; using Accu = AccuTypeCate::Add; // ... }; struct PMulAccu : virtual public AccPolicy { using MajorClass = AccPolicy; using MinorClass = AccPolicy::AccuTypeCate; using Accu = AccuTypeCate::Mul; }
代码讲解:
这段代码展示了一个使用虚继承的示例,通过使用虚继承,可以避免菱形继承(diamond inheritance)问题。
首先,我们定义了一个名为 `AccPolicy` 的结构体。在其中,有一个名为 `AccuTypeCate` 的嵌套结构体,它进一步定义了两个嵌套结构体 `Add` 和 `Mul`。`AccPolicy` 结构体还定义了一个类型别名 `Accu`,它被设置为 `AccuTypeCate::Add`。
接下来,我们定义了一个名为 `PMulAccu` 的结构体,并使用 `virtual` 关键字进行虚继承自 `AccPolicy`。这样一来,`PMulAccu` 结构体将作为虚继承链中的一个中间派生类。
在 `PMulAccu` 中,我们定义了三个类型别名:`MajorClass`、`MinorClass` 和 `Accu`。`MajorClass` 被设置为 `AccPolicy`,`MinorClass` 被设置为 `AccPolicy::AccuTypeCate`,而 `Accu` 被设置为 `AccuTypeCate::Mul`。
代码的执行过程和含义:
1. 首先,由于我们没有直接使用 `AccPolicy` 或 `PMulAccu` 的对象,所以在主函数中没有实例化任何对象的操作。
2. 在 `AccPolicy` 中,`AccuTypeCate::Add` 和 `AccuTypeCate::Mul` 分别代表 `AccuTypeCate` 内的两个嵌套结构体 `Add` 和 `Mul`。这两个结构体目前并没有定义具体内容。
3. `AccPolicy` 中的 `Accu` 类型别名被设置为 `AccuTypeCate::Add`。这意味着在 `AccPolicy` 中使用 `Accu`,实际上是指向 `AccuTypeCate::Add` 这个类型。
4. 在 `PMulAccu` 中,它通过虚继承(`virtual public AccPolicy`)继承了 `AccPolicy`。
5. `PMulAccu` 中的 `MajorClass` 类型别名被设置为 `AccPolicy`,这意味着它引用了 `AccPolicy` 这个基类。
6. `PMulAccu` 中的 `MinorClass` 类型别名被设置为 `AccPolicy::AccuTypeCate`,这意味着它引用了 `AccuTypeCate` 这个嵌套结构体。
7. `PMulAccu` 中的 `Accu` 类型别名被设置为 `AccuTypeCate::Mul`,这意味着它引用了 `AccuTypeCate` 中的 `Mul` 这个嵌套结构体。
通过使用虚继承,`PMulAccu` 结构体可以避免菱形继承问题。虚继承确保 `AccPolicy` 这个基类只被实例化一次,而不会在继承链中重复出现,从而避免了冗余和二义性问题。
三、policy选择元函数
Policy 模板是一种 C++ 设计模式,通常用于实现灵活的策略选择。在使用 Policy 模板时,可能会使用元函数来选择合适的策略。元函数是在编译时执行的函数,其结果可以用作模板参数。对于 Policy 模板的对外接口和 policy 选择元函数,下面进行详细介绍。
对外接口是指 Policy 模板向外部提供的功能接口,而 policy 选择元函数是一种能够在编译期根据特定规则选择合适的策略并返回结果的函数。下面我们来介绍这两部分:
1. 对外接口:
- 对外接口是 Policy 模板向外部提供的可供用户调用的功能接口,通常是一些操作函数或者接口函数。这些函数可能会依赖于 Policy 参数,通过 policy 参数来调用不同的策略实现。
- 对外接口需要清晰地定义每个策略所需的接口,以及以何种方式将 Policy 参数传递给内部的策略实现。
2. policy 选择元函数:
- policy 选择元函数是在编译时执行的函数,其目的是根据一定的条件或规则来选择合适的策略并返回其结果。这个选择是在编译期进行的,因此可以根据不同的条件选择不同的策略,这种灵活性非常有用。
- 通常,policy 选择元函数会使用一些模板元编程技术,例如 constexpr 函数、模板元编程的条件分支、类型 Traits 等来进行策略选择。
例如,一个 policy 选择元函数可能会接受一些参数,并依据这些参数的特性来选择合适的策略。根据参数的不同,返回不同策略的类型或标签,从而影响 Policy 模板中的行为。
总的来说,对外接口需要清晰地定义策略所需的功能,而 policy 选择元函数需要根据某些条件在编译期间动态地选择适当的策略。这种方式为 Policy 模板提供了高度的灵活性,并且允许在编译时根据特定情况选择策略,而不需要在运行时进行判断。
3.1 C++代码示例
#include <iostream> #include <type_traits> // Policy 1 struct Policy1 { static void doSomething() { std::cout << "Policy 1 is used." << std::endl; } }; // Policy 2 struct Policy2 { static void doSomething() { std::cout << "Policy 2 is used." << std::endl; } }; // Policy Selection Metafunction template <int N> struct PolicySelector { using type = Policy1; // Default policy }; template <> struct PolicySelector<2> { using type = Policy2; }; // Policy selection function template <int N> void executePolicy() { typename PolicySelector<N>::type::doSomething(); } int main() { executePolicy<1>(); // Select policy 1 executePolicy<2>(); // Select policy 2 return 0; }
代码步骤
- 首先,我们在 main 函数中调用了 executePolicy 函数,传入参数 1 和 2。这将导致选择不同的策略进行执行。
- 当调用 executePolicy<1>() 时,会根据元函数 PolicySelector 中的默认定义,选择使用 Policy1,并输出 “Policy 1 is used.”。
- 当调用 executePolicy<2>() 时,会根据元函数 PolicySelector 的特化版本,选择使用 Policy2,并输出 “Policy 2 is used.”。
3.2 policyContainer数组容器
PolicyContainer 是一种数组容器的设计模式,它使用 Policy 模板来实现灵活的策略选择,并且将选择的策略应用于数组容器中的元素。PolicyContainer 的目的是使容器的行为具有可配置性和可扩展性,而不需要修改容器本身的实现。
PolicyContainer 的实现步骤:
1. 定义策略模板:
- 首先,我们需要定义表示不同策略的策略模板。策略模板是一个模板类,可以根据实际需要定义不同的策略。
- 每个策略模板应该定义适当的接口函数,以供容器在使用不同策略时进行调用。
2. 定义容器类:
- 定义一个容器类,它包含一个元素数组来存储数据。
- 在容器类中,我们使用一个模板参数来表示策略模板,并通过该参数来选择具体的策略。
- 容器类可以定义一些常用的操作函数,如添加元素、访问元素、删除元素等。
3. 在容器类中使用策略:
- 在容器类的操作函数中,我们可以使用策略模板的接口函数来实现特定的操作逻辑。
- 这样,当我们使用容器操作元素时,将会根据所选择的策略来执行相应的行为。
4. 在客户端代码中使用容器:
- 在客户端代码中,我们可以通过实例化容器类并指定所需的策略来使用容器。
- 这样,我们就可以根据不同的需求选择不同的策略,并且策略的应用会自动应用到容器的操作中。
总的来说,PolicyContainer 类似于一个常规的容器,但通过使用 Policy 模板使得容器的行为具有灵活性和可配置性。PolicyContainer 可以根据不同的策略来选择相应的行为,并将其应用于容器中的元素。
需要注意的是,PolicyContainer 是一种设计模式,它提供了一种组织策略和容器的方式,可以根据具体的需求进行适当的调整和扩展。
3.2.1 C++代码示例
#include <iostream> #include <vector> // Policy 1 struct Policy1 { static void strategy(int value) { std::cout << "Policy 1: " << value << std::endl; } }; // Policy 2 struct Policy2 { static void strategy(int value) { std::cout << "Policy 2: " << value * value << std::endl; } }; // PolicyContainer template <typename Policy> class PolicyContainer { public: void addElement(int value) { elements.push_back(value); } void processElements() { for (const auto& element : elements) { Policy::strategy(element); } } private: std::vector<int> elements; }; int main() { PolicyContainer<Policy1> container1; container1.addElement(5); container1.addElement(10); container1.processElements(); PolicyContainer<Policy2> container2; container2.addElement(5); container2.addElement(10); container2.processElements(); return 0; }
输出结果:
Policy 1: 5 Policy 1: 10 Policy 2: 25 Policy 2: 100
3.3 NSPolicySelect::Selector_
书中代码示例
template <typename TMajorClass, typename TPolicyContainer> struct Selector_; template <typename TMajorClass, typename...TPolicies> struct Selector_<TMajorClass, PolicyContainer<TPolicies...>> { using TMF = typename MajorFilter_<PolicyContainer<>, TMajorClass, TPolicies...>::type; static_assert(MinorCheck_<TMF>::value, "Minor class set conflict!"); using type = std::conditional_t<IsArrayEmpyy<TMF>, TMajorClass, PolicySelRes<TMF>>; };
代码讲解:
以下是对给定代码的逐步解释:
1. 首先,定义了一个模板结构体 `Selector_`,它有两个模板参数 `TMajorClass` 和 `TPolicyContainer`。
2. 在 `Selector_` 结构体的实现中,它使用了另一个模板结构体 `MajorFilter_` 和另一个模板类 `PolicyContainer`。
3. 在 `Selector_` 结构体中,使用 `MajorFilter_` 元函数来过滤出与 `TMajorClass` 相关的主要策略。
4. 通过 `MajorFilter_` 元函数获取到的策略集合 `TMF`,会作为参数传递给 `MinorCheck_` 元函数。
5. `MinorCheck_` 元函数在编译时检查 `TMF` 集合中的次要策略是否有冲突。
6. 如果 `MinorCheck_` 元函数返回的 `value` 成员变量为 `false`,则会触发静态断言,并输出 "Minor class set conflict!" 的错误信息。
7. 最后,根据 `IsArrayEmpyy<TMF>` 的结果来决定策略选择的逻辑:
- 如果 `TMF` 集合为空,即 `IsArrayEmpyy<TMF>` 为真,那么选择使用 `TMajorClass` 作为最终的策略结果;
- 否则,选择使用 `PolicySelRes<TMF>` 作为最终的策略结果。
上述代码片段涉及到了多个模板结构体和模板类,并使用了元函数来进行策略过滤和冲突检查。其中的 `TMF` 是从 `MajorFilter_` 元函数获取的主要策略集合类型。
这些代码示例表明了使用模板结构体和元函数来实现策略选择过程。通过元函数的筛选和冲突检查,可以实现灵活且可扩展的策略选择机制。在使用时,我们可以将所需的主要类 `TMajorClass` 和策略容器 `TPolicyContainer` 作为模板参数传递给 `Selector_` 结构体,从而获取到最终的策略类型。
3.4 MinorCheck_元函数
MinorCheck_ 是一个元函数,它用于在编译时进行判断,并根据条件返回不同的类型。在 C++ 模板元编程中,元函数是在编译时执行的函数,它接受模板参数作为输入,并产生一个类型作为输出。
示例:
template <int N> struct MinorCheck_ { using Result = typename std::conditional<(N < 10), SmallType, BigType>::type; };
在这个示例中,MinorCheck_ 是一个接受整数模板参数 N 的元函数。根据条件 N < 10,它使用 std::conditional 模板进行条件判断,并将相应的类型赋值给 Result。如果 N 小于 10,则 Result 类型为 SmallType;否则为 BigType。
使用 MinorCheck_ 元函数时,可以根据具体的模板参数 N 在编译时确定 Result 类型,从而在代码中使用这个确定的类型。
总之,MinorCheck_ 元函数可以根据输入的模板参数在编译时进行判断,生成不同的类型作为输出,从而实现基于条件的类型选择。这种方式可以在编译期间根据条件来选择不同的编译策略,提高代码的灵活性和性能。
3.5 构造最终的返回类型
看原文!!!
四、使用宏简化policy对象的声明
当我们需要使用宏来简化 Policy 对象的声明时,可以使用宏来自动生成一系列的代码片段。
示例:
// 定义宏来自动生成 Policy 对象的声明 #define DECLARE_POLICY_OBJECT(Type) \ Type policy_##Type; // 使用宏来声明 Policy 对象 DECLARE_POLICY_OBJECT(Policy1) DECLARE_POLICY_OBJECT(Policy2) DECLARE_POLICY_OBJECT(Policy3)
示例中,我们定义了 `DECLARE_POLICY_OBJECT` 宏,该宏接受一个参数 `Type`,然后生成对应的 Policy 对象的声明代码。通过调用该宏,我们可以在代码中快速声明多个不同类型的 Policy 对象。
例如,当我们调用 `DECLARE_POLICY_OBJECT(Policy1)` 时,宏会展开为 `Policy1 policy_Policy1;`,这样就完成了 Policy1 对象的声明。同理,调用其他的宏也可以生成其他 Policy 对象的声明。
使用宏来简化 Policy 对象的声明可以减少重复的代码,提高代码的可读性和可维护性。当我们需要声明多个 Policy 对象时,可以使用宏来批量生成声明代码,降低了手动编写代码的工作量,提高了开发效率。
总结
同学们好好学,后续就是对第二章的总结以及练习题。让我们一起加油掌握元模板吧!!!