C++模板元模板(异类词典与policy模板)- - - 中篇

2023-11-08 04:36

本文主要是介绍C++模板元模板(异类词典与policy模板)- - - 中篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、键的表示

1.1 数值冲突问题

1.2 标识符管理

2.3 标识符管理方案

2.4 如何将字符串类型作为模板参数传递?

2.5 字符串字面值

2.6 字符串作为VarTypeDict键的麻烦之处

2.7 用类(或者结构体)的名字作为键

二、VarTypeDict的性能简析

三、用std::tuple作为缓存

总结


一、键的表示

代码示例:

#include <iostream>constexpr int A = 0;
constexpr int B = 1;
constexpr int Weight = 2;template <int A, int B, int Weight>
struct VarTypeDict {// 在这里可以使用 A、B、Weight 作为编译期常量
};int main() {VarTypeDict<A, B, Weight> myVarTypeDict;// 可以在这里使用 myVarTypeDict 进行操作return 0;
}

1.1 数值冲突问题

使用 C++ 中的枚举类(enum class)来为 A、B 和 Weight 分别创建独立的类型,从而避免数值冲突。

示例代码:

#include <iostream>enum class A : int { Value = 0 };
enum class B : int { Value = 1 };
enum class Weight : int { Value = 2 };template <typename T>
struct VarTypeDict {// 在这里可以使用 T::Value 作为编译期常量
};int main() {VarTypeDict<A> varA;VarTypeDict<B> varB;VarTypeDict<Weight> varWeight;// 可以在这里使用 varA、varB、varWeight 进行操作return 0;
}

示例中,我们使用枚举类 `enum class` 来分别表示 A、B 和 Weight,并给它们赋予各自独立的作用域。每个枚举类都有一个名为 Value 的成员,用来代表具体的值。这样,在某个函数中调用 Set 或 Get 时,通过传入枚举类的类型,编译器就可以明确地区分是要对哪个成员变量进行设置或读取。

1.2 标识符管理

要实现一种管理标识符的机制,可以通过定义一个中央管理的模块或者类来完成。这个模块或者类可以负责分配、记录和管理各种标识符,同时提供接口供其他模块或者类来注册和查询已分配的标识符。

代码示例:

#include <iostream>
#include <map>
#include <string>// 枚举类用于表示不同的标识符
enum class Identifier {ID_A,ID_B,ID_C,// ... 其他标识符
};// 标识符管理类
class IdentifierManager {
public:// 注册标识符及其含义static void registerIdentifier(Identifier id, const std::string& meaning) {identifierMap()[id] = meaning;}// 查询标识符的含义static std::string getIdentifierMeaning(Identifier id) {auto it = identifierMap().find(id);if (it != identifierMap().end()) {return it->second;} else {return "Undefined";}}private:// 获取标识符映射static std::map<Identifier, std::string>& identifierMap() {static std::map<Identifier, std::string> map;return map;}
};int main() {// 注册标识符及其含义IdentifierManager::registerIdentifier(Identifier::ID_A, "This is A");IdentifierManager::registerIdentifier(Identifier::ID_B, "This is B");// 查询标识符的含义std::cout << "ID_A means: " << IdentifierManager::getIdentifierMeaning(Identifier::ID_A) << std::endl;std::cout << "ID_B means: " << IdentifierManager::getIdentifierMeaning(Identifier::ID_B) << std::endl;std::cout << "ID_C means: " << IdentifierManager::getIdentifierMeaning(Identifier::ID_C) << std::endl;return 0;
}

在这个示例中,我们定义了一个枚举类 `Identifier` 用于表示不同的标识符,然后实现了一个名为 `IdentifierManager` 的类来管理这些标识符。`IdentifierManager` 类提供了注册标识符和查询标识符含义的接口,以及一个内部的标识符映射表作为静态成员函数,用于记录标识符和其对应的含义。

在 `main` 函数中,我们通过 `IdentifierManager` 类注册了标识符 A 和 B 并分别为它们指定了含义,然后进行了含义的查询打印输出。在大型项目中,可能需要更复杂的机制来管理标识符,比如考虑分配范围、协作规范、版本控制等方面的需求。

2.3 标识符管理方案

例子:

1. 自动生成标识符:可以考虑编写工具或脚本来自动生成标识符及其含义的映射,这样可以减少手动管理的工作量。例如,可以创建一个配置文件,其中列出了所有标识符以及它们的含义,然后编写一个工具来读取配置文件并生成对应的映射代码。

2. 使用符号表或数据库:将标识符及其含义存储在集中的符号表或数据库中。其他模块可以通过查询这些符号表或数据库来获取标识符的含义,而不需要维护自己的映射表。

3. 采用语义化的标识符命名规范:通过采用语义化的命名规范,可以尽量减少对标识符的需要人为管理。这样的命名规范可以让标识符的含义更直观明了,减少了映射的管理工作。

4. 版本控制及代码审查: 在多人协作开发时,通过严格的版本控制规范和代码审查机制,可以帮助团队更好地了解和协调各部分的标识符使用情况,及时发现潜在的冲突和问题。

5. 定期清理未使用的标识符:定期对系统中未使用的标识符进行清理,避免映射表中出现大量无用的条目,降低管理成本。

以上的建议旨在减少标识符管理的维护成本,提高代码的可维护性和开发效率。

2.4 如何将字符串类型作为模板参数传递?

在 C++ 中,标准库中的 `std::string` 类型本身并不能直接作为模板参数传递,因为模板参数的接受范围有一定限制。C++模板参数有两种:类模板参数和非类型模板参数。对于非类型模板参数,可以接受的类型主要包括整型、枚举、指针、引用等,而并不包括类类型(class type),因此`std::string`这样的类类型并不能直接作为非类型模板参数。

不过,C++11 引入了模板别名(Template Aliases)和变长参数模板(Variadic Templates)这两个特性,使得我们可以通过一些间接方式间接实现类似于传递字符串这样的非类型参数:

1. 模板别名:通过使用模板别名,可以针对特定的字符串类型定义别名,然后将别名作为模板参数传递。

例如:

   template <typename T, T val>struct MyStruct {};using MyString = std::integral_constant<const char*, "hello">;MyStruct<MyString, MyString::value> obj;

2. 变长参数模板:通过变长参数模板可以实现对字符串的转化,进而作为模板参数传递。

例如:

 template <char... Args>struct StringHolder { static constexpr const char value[] = {Args..., '\0'}; };StringHolder<'h', 'e', 'l', 'l', 'o'> obj;

或者,如果场景允许,可以考虑使用模板元编程技术,结合特化、重载等手段,以达到传递字符串(如限定字符数组)作为模板参数的目的。不过,这种方法可能引入复杂性和限制。

2.5 字符串字面值

C++11标准引入了对字符串字面值(string literals)的非类型模板参数支持,允许将字符串字面值作为模板参数传递。这样的话,我们就可以在模板实例化的过程中使用不同的字符串字面值来生成不同的实例,从而实现对字符串类型的模板化。

1. 采用引用的方式声明字符串:

   template <const char (&str)[N]>struct MyStruct {// ...};const char hello[] = "Hello";MyStruct<hello> obj1;const char cpp[] = "C++";MyStruct<cpp> obj2;

在这种方式下,不同长度的字符串会被视为不同的类型,从而可以实现不同长度字符串的区分。

2. 采用值的方式声明字符串:

  template <const char* str>struct MyStruct {// ...};MyStruct<"Hello"> obj1;MyStruct<"C++"> obj2;

在这种方式下,字符串字面值会被蜕化为相应的指针类型,因此无论字符串的长度如何,其类型都会被蜕化为 `const char*`,因此相同长度的字符串会被视为相同的类型。

值得注意的是,使用字符串字面值作为非类型模板参数时需要特别小心,因为字符串字面值的地址在不同编译单元中可能是不同的,这可能会导致一些潜在的问题。在实际使用时,建议谨慎考虑这种方式的适用性,并对潜在的问题有所准备。

希望这些信息能解答您的疑问。如果您对此有更多的问题,或者有其他方面的疑问,欢迎继续提问。

2.6 字符串作为VarTypeDict键的麻烦之处

主要的问题包括:

1. 内存开销: 使用字符串作为键可能会导致额外的内存开销,因为需要存储字符串本身,并且字符串在内存中是不连续的,因此可能会引入额外的碎片化问题。

2. 比较开销:字符串之间的比较通常需要逐个字符比较,这会导致比较开销较大,尤其是在大量数据的情况下。

3. 哈希计算开销:如果需要对字符串进行哈希计算来实现快速查找,那么字符串的哈希计算也会带来一定的开销。

4. 复杂性:使用字符串作为键可能引入额外的复杂性,包括对字符串的拷贝、比较、哈希计算等操作,以及对冲突处理的考虑。

基于上述问题,在实际应用中,如果性能要求较高,可能会倾向于使用整数类型作为键,因为整数类型的比较和哈希计算通常比字符串更为高效。

当然,并非所有情况下都会选择整数作为键,特别是在需要描述性强、易于理解的情况下,使用字符串作为键是很自然的选择。在这种情况下,可能需要综合考虑性能、可读性和维护成本等因素,选择最适合的数据结构或算法。。

2.7 用类(或者结构体)的名字作为键

实际上,类(或者结构体)的名字作为键是一种非常自然且优雅的做法。由于类名在编译期间就已经确定,并且是唯一的,可以作为在编译器中使用的键。

这样做有几个优点:

1. 唯一性和确定性:每个类名在程序中是唯一且确定的,确保了键的唯一性。

2. 易于比较:类名之间的比较是直接的、高效的,因为它们在编译期就已经确定,不需要进行额外的运行时计算。

3. 类型安全:类名作为键具有很强的类型安全性,因为类名的类型是静态确定的,不会出现类型差异导致的问题。

4. 可读性:在使用代码时,类名作为键能够直观地表示所指代的类型,提高了代码的可读性和可维护性。

在C++中,可以使用模板元编程来实现类名作为键的映射,比如使用模板元编程技术或者类型映射表(type mapping table)等方法,将类名映射到相应的数值或其他信息上。这种方式能够在一定程度上避免了使用字符串作为键带来的问题,并且能够更好地利用C++的静态类型系统。

代码示例:

#include <iostream>
#include <string>// 主模板
template <typename T>
struct TypeToIntMap {// 通用的模板,没有定义任何内容
};// 特化模板,将类名映射到整数
template <typename T>
struct TypeToIntMap {static const int value;
};template <typename T>
const int TypeToIntMap<T>::value = 0;// 在这里进行特化,将类名映射到具体的整数
template <>
struct TypeToIntMap<int> {static const int value;
};const int TypeToIntMap<int>::value = 1;template <>
struct TypeToIntMap<double> {static const int value;
};const int TypeToIntMap<double>::value = 2;// 测试
int main() {// 使用 TypeToIntMapstd::cout << "int is mapped to: " << TypeToIntMap<int>::value << std::endl;std::cout << "double is mapped to: " << TypeToIntMap<double>::value << std::endl;return 0;
}

示例中,我们定义了一个 TypeToIntMap 模板,用于将不同类型映射到不同的整数上。对于每个需要映射的类型,我们使用模板特化来为其分配一个唯一的整数值。

在 main 函数中,我们展示了如何使用 TypeToIntMap 来获取不同类名所对应的整数值。当您编译并运行这段代码时,您会看到类名被成功映射到了相应的整数值上。

二、VarTypeDict的性能简析

内容剖析:

VarTypeDict 类通过在编译期进行键到数值的映射,实现了优越的性能和效率,相较于在运行期构造的 std::map 或类似数据结构而言,具有明显的优势。

在编译期就完成了的键的映射,对于性能和资源的利用都有很大的好处。

1. 编译期计算:VarTypeDict 在编译期就完成了键到数值的映射,因此避免了运行时的比较和计算,提高了程序的执行效率。

2. 优化机会:在编译期进行的计算通常可以得到更好的优化,甚至可以在特定情况下完全消除中间量的存储,极大地减少了对内存的占用。

3. 运行期等价物无法比拟:编译期计算带来的性能优势是运行期等价物所无法比拟的,这反映了在编译期进行计算的强大之处。

总的来说,VarTypeDict 类通过充分利用编译期计算的特性,实现了高效的键值映射,并在性能上远优于运行期构造的标准库容器。这种利用编译期进行计算的思想,是现代 C++ 中倡导的“在编译期进行尽可能多的工作”的一部分。

三、用std::tuple作为缓存

std::shared_ptr<void> m_tuple{sizeof...(TTypes)}; 
std::tuple<<TType...> m_tuple;

首先,需要指出的是 std::shared_ptr<void> 是一个智能指针,它可以指向任意类型的对象,而 std::tuple<TTypes...> 则是一个元组类型,它包含了一组不同类型的值。

使用 std::tuple 可能会引入更复杂的更新逻辑,特别是当需要在元组中更新值时,因为元组中的值类型可能是不同的,这可能会导致更新逻辑的复杂性增加。

由于类型不同导致赋值操作需要对数组中的每个元素逐一拷贝或移动,这将会导致性能上的显著损失。

可以考虑使用一些优化手段来尽量减少这种开销,比如使用移动语义(move semantics)来减少数据移动的代价,或者利用懒惰求值(lazy evaluation)的思想来延迟实际的数据复制操作。


总结

异类词典的实现内容较多,将在下期作为单独讲解!!!

这篇关于C++模板元模板(异类词典与policy模板)- - - 中篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/367897

相关文章

Python中Flask模板的使用与高级技巧详解

《Python中Flask模板的使用与高级技巧详解》在Web开发中,直接将HTML代码写在Python文件中会导致诸多问题,Flask内置了Jinja2模板引擎,完美解决了这些问题,下面我们就来看看F... 目录一、模板渲染基础1.1 为什么需要模板引擎1.2 第一个模板渲染示例1.3 模板渲染原理二、模板

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

利用Python打造一个Excel记账模板

《利用Python打造一个Excel记账模板》这篇文章主要为大家详细介绍了如何使用Python打造一个超实用的Excel记账模板,可以帮助大家高效管理财务,迈向财富自由之路,感兴趣的小伙伴快跟随小编一... 目录设置预算百分比超支标红预警记账模板功能介绍基础记账预算管理可视化分析摸鱼时间理财法碎片时间利用财

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

IDEA自动生成注释模板的配置教程

《IDEA自动生成注释模板的配置教程》本文介绍了如何在IntelliJIDEA中配置类和方法的注释模板,包括自动生成项目名称、包名、日期和时间等内容,以及如何定制参数和返回值的注释格式,需要的朋友可以... 目录项目场景配置方法类注释模板定义类开头的注释步骤类注释效果方法注释模板定义方法开头的注释步骤方法注

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve