dab-adapter-cpp学习:DAB::dabclient
概述
dab-adapter-cpp
是一个 C++ 编写的设备自动化总线(DAB)适配器模板,旨在简化 DAB 操作的实现,库处理消息层接口、解析和调度工作,方便开发者专注于所需 DAB 操作的实现。
DAB::dabclient
DAB::dabClient
是实现 DAB 接口所需基本功能的基类,使用 CRTP 模式继承,用于检测功能重写并自动填充 opList DAB 方法,具体实现类需从其继承。
def宏
def是 DAB::dabClient
的一个重要的宏,主要用于定义和初始化 DAB(Device Automation Bus)操作相关的信息,并将其插入到 dispatchMap 中。
- def宏的参数
#define METHODS \
def( "/operations/list", opList, opList, {}, {} ) \
def( "/applications/list", appList, appList, {}, {} ) \
def( "/applications/launch", appLaunch, appLaunch, {"appId"}, {"parameters"} ) \
def( "/applications/launch-with-content", appLaunchWithContent, appLaunchWithContent, ({ "appId", "contentId" }), { "parameters" } )
-
参数及作用
- methName
类型:字符串字面量
作用:表示 DAB 操作的方法名,例如 "/operations/list"、"/applications/list" 等。这个方法名会被用于构建最终的操作主题(如 "dab/" + deviceId + methName),用于在 dispatchMap 中作为键,以便后续的消息路由和处理。 - detectFunc
类型:函数名
作用:用于检测某个功能是否被重写。在创建 p1 时,会通过 std::is_same_v<decltype(&dabClient::detectFunc), decltype(&T::detectFunc)> 来判断该函数是否被重写,这对于确定某个 DAB 操作是否被用户实现的类重写很有用,从而影响是否将该操作添加到 dispatchMap 中。 - callFunc
类型:函数名
作用:表示实际要调用的函数。nativeDispatch 会存储这个函数指针,当对应的 DAB 操作被触发时,会调用这个函数来处理具体的业务逻辑。 - fixedParams
类型:初始化列表
作用:包含固定参数的列表。这些参数在调用 callFunc 时可能会被使用,例如在某些 DAB 操作中,可能需要特定的固定参数才能正确执行。fixedParams 的大小会用于确定 nativeDispatch 模板实例化时的固定参数数量。 - optionalParams
类型:初始化列表
作用:包含可选参数的列表。与 fixedParams 类似,但这些参数是可选的。optionalParams 的大小会用于确定 nativeDispatch 模板实例化时的可选参数数量。
- methName
-
def位置
在解读这段代码的时候,首先关注了def的位置
def的使用在def的定义后面,- 宏是在 C++ 编译的预处理阶段进行处理的。
预处理阶段是编译过程的第一个阶段,在这个阶段,预处理器会对源代码进行文本替换操作,而不是像编译和链接阶段那样进行语法分析、语义检查以及生成可执行代码等操作。 - 宏定义:
当预处理器遇到 #define 指令(如 #define def(...) ... )时,它会将宏名(这里是 def)及其参数列表和对应的替换文本记录下来。 - 宏展开:
在后续的代码中,当预处理器遇到宏名(如 def(...))时,它会将宏调用处的代码用宏定义中的替换文本进行替换。这个过程是简单的文本替换,不涉及语法和语义的检查。 - 在这段代码中,def 宏的定义和使用也是遵循同样的原则。
预处理器在处理代码时,会先记录 def 宏的定义,然后在后续遇到 def 宏的使用时,将其替换为相应的代码片段。由于预处理阶段只是简单的文本替换,不关心宏定义和使用的顺序,所以def 的使用可以放在 def 的定义后面。
- 宏是在 C++ 编译的预处理阶段进行处理的。
def宏的展开
在类的开始,定了了METHODS宏,METHODS宏使用def宏定义和初始化 DAB(Device Automation Bus)操作相关的信息。
def宏的在dabClient的构造函数中定义,METHODS宏也在此处展开
展开后的代码类似如下:
// 展开def( "/applications/launch-with-content", appLaunchWithContent, appLaunchWithContent, ({ "appId", "contentId" }), { "parameters" } )
{
auto disp = std::make_unique<nativeDispatch<std::initializer_list<char const *>{ "appId", "contentId" }.size(), std::initializer_list<char const *>{ "parameters" }.size(), T, decltype(&T::appLaunchWithContent)>> ( &T::appLaunchWithContent, std::vector<std::string_view>{ "appId", "contentId" }, std::vector<std::string_view>{ "parameters" } );
auto p1 = std::make_pair ( std::move ( disp ), !std::is_same_v<decltype(&dabClient::appLaunchWithContent), decltype(&T::appLaunchWithContent)> || !strcmp ( "/operations/list", ("/applications/launch-with-content") ) || !strcmp ( "/version", ("/applications/launch-with-content") ) );
auto p2 = std::make_pair ( std::string ( "dab/" ) + deviceId + ("/applications/launch-with-content"), std::move ( p1 ) );
dispatchMap.insert ( std::move ( p2) );
}
disp
的创建:- std::make_unique<nativeDispatch<...>> 用于创建一个 nativeDispatch 类型的智能指针 disp。
- std::initializer_list<char const *>{ "appId", "contentId" }.size() 计算固定参数列表 { "appId", "contentId" } 的大小,std::initializer_list<char const *>{ "parameters" }.size() 计算可选参数列表 { "parameters" } 的大小。
- &T::appLaunchWithContent 是要调用的函数指针,同时将固定参数列表 { "appId", "contentId" } 和可选参数列表 { "parameters" } 转换为 std::vectorstd::string_view 传递给 nativeDispatch 的构造函数。
- p1 的创建:
- std::make_pair 创建一个 pair 对象 p1,第一个元素是 std::move(disp),即移动 disp 的所有权。
- 第二个元素是一个布尔值,用于判断 appLaunchWithContent 函数是否被重写,同时还会检查方法名是否为 "/operations/list" 或 "/version"。
- p2 的创建:
- 再次使用 std::make_pair 创建一个 pair 对象 p2,第一个元素是 "dab/" + deviceId + "/applications/launch-with-content",即构建完整的操作主题。
- 第二个元素是 std::move(p1),即移动 p1 的所有权。
- 插入到 dispatchMap 中:
dispatchMap.insert(std::move(p2)) 将 p2 移动插入到 dispatchMap 中,用于后续的消息路由和处理。
nativeDispatch类
template< size_t nFixed, size_t nOptional, typename T, class R, class C, class ... Args >
struct nativeDispatch<nFixed, nOptional, T, R ( C::* ) ( Args... )> : public dispatcher<T>
{
nativeDispatch ()
{
// should never be called
assert ( false );
}
// ...
};
类的模板参数
nativeDispatch
类是dispatcher
类的派生类,其目的是调用 C++ 方法,并从传入的 JSON 参数中提取参数来调用该方法,同时处理固定参数和可选参数。
nativeDispatch
类的模板参数说明如下:
nFixed
:表示固定参数的数量,这些参数的值在 JSON 中必须存在。
nOptional
:表示可选参数的数量,这些参数的值在 JSON 中可以不存在,若不存在则使用默认构造的值。
T
:用于调度的类的类型。
R ( C::* ) ( Args... )
:要调用的方法的函数指针,其中R是返回类型,C是类类型,Args...是参数包,表示该方法的参数类型列表。
构造函数参数
// the constructor takes the function pointer of the method to call, and a vector of fixed and a vector of optional parameters
nativeDispatch ( R ( C::*func ) ( Args... ), std::vector<std::string_view> const &fixedParams, std::vector<std::string_view> const &optionalParams ) : fixedParams ( fixedParams ), optionalParams ( optionalParams )
{
funcPtr = func;
}
用于初始化nativeDispatch对象,它接收特定的参数来配置该对象,以便能够正确地调用指定的成员函数并处理相应的参数。以下是该构造函数的详细介绍:
- R ( C::*func ) ( Args... )
这是一个指向类C的成员函数的指针,该成员函数的返回类型为R,参数列表为Args...。这个参数的作用是指定在进行调度时要调用的具体成员函数。
例如,假设有一个类MyClass,其中有一个成员函数void myMethod(int a, std::string b),那么R就是void,C就是MyClass,Args...就是int, std::string。 - std::vector<std::string_view> const &fixedParams
这是一个常量引用,指向一个存储std::string_view类型元素的向量。fixedParams向量包含了固定参数的名称列表。
固定参数是指在调用成员函数时必须提供的参数,这些参数在 JSON 数据中必须存在对应的键值对。
例如,如果成员函数myMethod的第一个参数a是固定参数,那么在fixedParams向量中可能会有一个元素对应着"a",表示在解析 JSON 数据时,需要查找键为"a"的值作为该参数的值。 - std::vector<std::string_view> const &optionalParams
这也是一个常量引用,指向一个存储std::string_view类型元素的向量。optionalParams向量包含了可选参数的名称列表。
可选参数是指在调用成员函数时可以不提供的参数,如果 JSON 数据中没有对应的键值对,则可以使用默认值。
例如,如果成员函数myMethod的第二个参数b是可选参数,那么在optionalParams向量中可能会有一个元素对应着"b",表示在解析 JSON 数据时,会查找键为"b"的值作为该参数的值,如果没有找到,则使用默认值。
最后两个模板参数的推导
编译器会根据appLaunchWithContent
函数的参数来推导nativeDispatch
类的最后两个模板参数。
在代码中,nativeDispatch
类有一个特化版本:
template< size_t nFixed, size_t nOptional, typename T, class R, class C, class ... Args >
struct nativeDispatch<nFixed, nOptional, T, R ( C::* ) ( Args... )> : public dispatcher<T>
{
// ...
};
当使用nativeDispatch
来处理像appLaunchWithContent
这样的成员函数时,编译器会根据appLaunchWithContent
的函数签名来推导模板参数。
例如,appLaunchWithContent函数的定义如下:
jsonElement appLaunchWithContent ( std::string const &appId, std::string const &contentId, jsonElement const &elem )
{
throw dabException{501, "unsupported"};
}
这里,R
会被推导为jsonElement
(函数的返回类型),C
会被推导为包含appLaunchWithContent
的类类型(如果appLaunchWithContent
是某个类的成员函数),Args...
会被推导为std::string const &, std::string const &, jsonElement const &
(函数的参数类型列表)。
这样,编译器就可以根据具体的函数签名来实例化nativeDispatch
类的特化版本,从而实现对不同函数的适配和调用。
总结来说,编译器能够根据具体的成员函数参数和返回类型,自动推导出nativeDispatch
类模板所需的参数,以正确实例化该类。
特化发生的时机
nativeDispatch
类的特化是在编译期,当编译器遇到需要实例化nativeDispatch
类的代码时,根据具体的模板参数来选择合适的特化版本。具体来说,当创建nativeDispatch
对象并传递具体的函数指针(如指向appLaunchWithContent
的函数指针)时,编译器会根据这个函数的签名(返回类型、所属类类型以及参数类型)来确定特化版本。
例如,在代码中可能会有类似这样的创建nativeDispatch
对象的代码:
// 假设T是某个类类型
R (C::*funcPtr)(Args...) = &appLaunchWithContent;
std::vector<std::string_view> fixedParams = {"appId", "contentId"};
std::vector<std::string_view> optionalParams = {"parameters"};
nativeDispatch<nFixed, nOptional, T, funcPtr>(funcPtr, fixedParams, optionalParams);
在这个过程中,编译器会根据funcPtr
所指向的appLaunchWithContent
函数的具体信息(返回类型、所属类类型以及参数类型)来实例化nativeDispatch
类的特化版本。
std::string_view
std::string_view
是 C++17
引入的一个类,它提供了一种轻量级的方式来处理字符串
,主要有以下作用:
- 避免不必要的字符串拷贝
在许多情况下,当我们只需要读取字符串的内容而不需要拥有它的所有权时,使用std::string
会导致不必要的内存拷贝。std::string_view
只包含一个指向字符串数据的指针和字符串的长度,不会进行数据的拷贝,因此可以显著提高性能。
例如:
#include <iostream>
#include <string_view>
void printStringView(std::string_view sv) {
std::cout << sv << std::endl;
}
int main() {
std::string str = "Hello, World!";
printStringView(str); // 不会进行字符串拷贝
printStringView("Hello, C++!"); // 直接处理字符串字面量,无拷贝
return 0;
}
- 作为函数参数
当函数只需要读取字符串内容时,使用std::string_view
作为参数类型可以接受多种字符串类型,如std::string
、const char*
等,增加了函数的灵活性。
#include <iostream>
#include <string_view>
void printLength(std::string_view sv) {
std::cout << "Length: " << sv.length() << std::endl;
}
int main() {
std::string str = "Hello";
const char* cstr = "World";
printLength(str); // 可以接受 std::string
printLength(cstr); // 可以接受 const char*
return 0;
}
- 处理子字符串
std::string_view
可以方便地表示字符串的一部分,而不需要额外的内存分配。
#include <iostream>
#include <string_view>
int main() {
std::string str = "Hello, World!";
std::string_view sv = str;
std::string_view sub_sv = sv.substr(7, 5); // 表示 "World"
std::cout << sub_sv << std::endl;
return 0;
}
- 提高代码的效率
在一些需要频繁处理字符串的场景中,使用std::string_view
可以减少内存分配和拷贝的开销,从而提高代码的效率。
#define def( methName, detectFunc, callFunc, fixedParams, optionalParams ) \
{ \
auto disp = std::make_unique<nativeDispatch<std::initializer_list<char const *>fixedParams.size (), std::initializer_list<char const *>optionalParams.size (), T, decltype(&T::callFunc)>> ( &T::callFunc, std::vector<std::string_view> fixedParams, std::vector<std::string_view> optionalParams ); \
auto p1 = std::make_pair ( std::move ( disp ), !std::is_same_v<decltype(&dabClient::detectFunc), decltype(&T::detectFunc)> || !strcmp ( "/operations/list", (methName) ) || !strcmp ( "/version", (methName) ) ); \
auto p2 = std::make_pair ( std::string ( "dab/" ) + deviceId + (methName), std::move ( p1 ) ); \
dispatchMap.insert ( std::move ( p2) ); \
}
这里使用 std::vector<std::string_view>
来传递固定参数和可选参数列表,避免了不必要的字符串拷贝,提高了代码的性能。
总之,std::string_view
是一种高效、灵活的字符串处理工具,适用于各种只读字符串操作的场景。
std::initializer_list
std::initializer_list
是 C++11
引入的一个轻量级容器,它提供了一种方便的方式来传递和初始化对象列表。
以下是关于 std::initializer_list
的详细介绍:
- 基本概念
- 定义:
std::initializer_list
是一个标准库类型,用于表示某种特定类型的值的数组。它可以用于构造函数、函数参数等场景,使得代码在处理多个同类型的值时更加简洁。 - 特点:
std::initializer_list
对象是常量,一旦初始化后,其内容就不能被修改。
它是一个轻量级的容器,不拥有元素的所有权,只是引用传递给它的元素。
- 定义:
- 语法
std::initializer_list<T>
其中 T
是列表中元素的类型。
- 常见用法
- 构造函数初始化
可以使用std::initializer_list
来实现构造函数的列表初始化。例如:
- 构造函数初始化
#include <vector>
class MyClass {
public:
MyClass(std::initializer_list<int> list) {
for (int num : list) {
std::cout << num << " ";
}
std::cout << std::endl;
}
};
int main() {
MyClass obj = {1, 2, 3, 4, 5}; // 使用 initializer_list 初始化对象
return 0;
}
在这个例子中,MyClass
的构造函数接受一个 std::initializer_list<int>
类型的参数,使得我们可以用花括号 {} 来初始化 MyClass
的对象。
- 函数参数
函数可以接受std::initializer_list
作为参数,这样在调用函数时可以方便地传递多个同类型的值。例如:
#include <iostream>
void printNumbers(std::initializer_list<int> numbers) {
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
}
int main() {
printNumbers({10, 20, 30, 40, 50}); // 传递 initializer_list 作为参数
return 0;
}
- 容器初始化
许多标准库容器都支持使用std::initializer_list
进行初始化。例如:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5}; // 使用 initializer_list 初始化 vector
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
- 成员函数
- size():返回列表中元素的数量。
- begin():返回指向列表中第一个元素的迭代器。
- end():返回指向列表中最后一个元素之后位置的迭代器。
例如:
#include <iostream>
#include <initializer_list>
int main() {
std::initializer_list<int> numbers = {1, 2, 3, 4, 5};
std::cout << "Size: " << numbers.size() << std::endl;
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
总之,std::initializer_list
为 C++ 提供了一种方便、统一的方式来处理初始化列表,使得代码更加简洁和易读。
std::is_same_v
std::is_same_v
是 C++17
引入的一个类型特征(type trait),用于在编译时检查两个类型是否相同。它是 std::is_same
的便捷版本。
- 基本概念
- 定义:
std::is_same_v
是一个编译时的类型检查工具,用于判断两个类型是否完全相同。它返回一个布尔值常量,如果两个类型相同则返回 true,否则返回 false。 - 与
std::is_same
的关系:在C++11
及C++14
中,要进行类型相同的检查,通常使用std::is_same
,它是一个类模板,使用时需要通过::value
来获取布尔结果。而C++17
引入了std::is_same_v
作为std::is_same<T, U>::value
的别名,简化了代码的编写。
- 定义:
std::is_same_v<T, U>
其中 T 和 U 是要比较的两个类型。
- 常见用途
- 模板元编程:在模板元编程中,经常需要根据类型的不同进行不同的处理,std::is_same_v 可以帮助我们在编译时做出决策。例如:
#include <iostream>
#include <type_traits>
template <typename T>
void printTypeInfo() {
if constexpr (std::is_same_v<T, int>) {
std::cout << "这是一个 int 类型" << std::endl;
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "这是一个 double 类型" << std::endl;
} else {
std::cout << "这是其他类型" << std::endl;
}
}
int main() {
printTypeInfo<int>();
printTypeInfo<double>();
printTypeInfo<char>();
return 0;
}
在这个例子中,根据不同的类型,printTypeInfo
函数会输出不同的信息。
- 编译时类型检查:在函数或类模板中,可以使用
std::is_same_v
来确保传入的类型符合要求,从而避免运行时错误。例如:
#include <iostream>
#include <type_traits>
template <typename T>
T add(T a, T b) {
static_assert(std::is_same_v<T, int> || std::is_same_v<T, double>, "只支持 int 和 double 类型");
return a + b;
}
int main() {
int result1 = add(1, 2);
double result2 = add(1.5, 2.5);
// 下面这行代码会在编译时出错,因为传入的是 char 类型
// char result3 = add('a', 'b');
return 0;
}
总之,std::is_same_v
是一个非常有用的类型特征,它可以帮助我们在编译时进行类型检查和决策,提高代码的安全性和可维护性。
- 为什么要引入 std::is_same_v
引入std::is_same_v
主要是为了提高代码的简洁性和可读性。在C++17
之前,使用std::is_same
进行类型比较时,需要显式地访问::value
成员,这会使代码显得冗长。而std::is_same_v
允许直接获取类型比较的结果,减少了代码量,使代码更加简洁易读。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤