我可以 "browse" 结构成员来简化构建 BDD class 吗?
Can I "browse" members of a struct to simplify building a BDD class?
我正在构建自定义 BDD class 来为我的程序存储不同类型的数据(例如,long
、char*
、double
、...)。
为了存储数据,我需要每个 table 的结构,如下所示:
struct MYSTRUCT0
{
char variable0[10];
char variable1[70];
};
struct MYSTRUCT1
{
long variable0;
long variable1;
char variable2[6];
double variable3;
};
但是每次我需要一个新的 table 时,工作量很大,因为我需要编写一个函数来将每个 table 保存在一个文件中,读取它等等。更糟糕的是,它不是真正面向对象。
所以我的问题是,有没有办法 "browse" 结构来简化我的代码?
像这样:
for(int v=0; v<arraysize; v++)
for(int i=0; i<MYSTRUC0.length; i++)
{
if (MYSTRUCT.getvar(i).type == long)
DoSomethingForLong(myarray(v).getval(i));
if (MYSTRUCT.getvar(i).type == char*)
DoSomethingForCharPtr(myarray(v).getval(i));
}
我知道这样的代码可以直接在 C++ 中运行。我只是用它来说明我的意思。
下面的代码只是一个示例,说明如何创建自己的 "variable-type-aware" 结构,这可能是您想要的:
#include <vector>
enum MyTypes
{
LONG,
CHARPTR,
DOUBLE,
} myTypes;
struct MyStruct
{
MyStruct(long longVar)
{
variable.longVar = longVar;
whichType = LONG;
}
MyStruct(char* charPtr)
{
variable.charPtr = charPtr;
whichType = CHARPTR;
}
MyStruct(double var)
{
variable.var = var;
whichType = DOUBLE;
}
~MyStruct()
{
}
MyTypes whichType;
union {
long longVar;
char* charPtr;
double var;
} variable;
};
void DoSomethingForLong(MyStruct* doubleStruct)
{
/*Do something specific to long*/
};
void DoSomethingForCharPtr(MyStruct* doubleStruct)
{
/*Do something specific to char pointer*/
};
void DoSomethingForDouble(MyStruct* doubleStruct)
{
/*Do something specific to double*/
};
int main()
{
std::vector<MyStruct*> myVec;
// add a struct with long variable
long longVar = 2000000000;
MyStruct* myLongStruct = new MyStruct(longVar);
myVec.push_back(myLongStruct);
// add a struct with char pointer
char* charArray = new char[1000];
MyStruct* myCharPtrStruct = new MyStruct(charArray);
myVec.push_back(myCharPtrStruct);
// add a struct with double variable
double doubleVar = 200.200;
MyStruct* myDoubleStruct = new MyStruct(doubleVar);
myVec.push_back(myDoubleStruct);
for (int i = 0; i < myVec.size(); ++i)
{
MyStruct* tempStruct = myVec[i];
if (tempStruct->whichType == LONG)
{
DoSomethingForLong(tempStruct);
}
else if (tempStruct->whichType == CHARPTR)
{
DoSomethingForCharPtr(tempStruct);
}
else if (tempStruct->whichType == DOUBLE)
{
DoSomethingForDouble(tempStruct);
}
}
if (myLongStruct)
{
delete myLongStruct;
myLongStruct = nullptr;
}
if (myCharPtrStruct)
{
if (charArray)
{
delete[] charArray;
charArray = nullptr;
}
delete myCharPtrStruct;
myCharPtrStruct = nullptr;
}
if (myDoubleStruct)
{
delete myDoubleStruct;
myDoubleStruct = nullptr;
}
}
如果您不厌其烦地添加一个可以将数据成员导出为元组的成员函数,那么我们可以使用一些模板元编程来完成这项工作。
Live Demo (C++14)
一、改动:
struct MYSTRUCT0
{
char variable0[10];
char variable1[70];
std::tuple<char(&)[10], char(&)[70]> GetData()
{
return std::tie(variable0, variable1);
}
};
struct MYSTRUCT1
{
long variable0;
long variable1;
char variable2[6];
double variable3;
std::tuple<long&, long&, char(&)[6], double&> GetData()
{
return std::tie(variable0, variable1, variable2, variable3);
}
};
std::tie
will put references to these members into a tuple
.
元组的好处在于它将所有类型编码到我们可以利用的列表中。 (您可能可以编写宏来为您创建这些结构。)
从这里开始,策略是编写一个可以处理任何元组的函数。
由于我们通过调用 std::get<i>
来访问元组的元素,其中 i
是某个索引,我们需要一种方法来获取这些元素的索引,因此我们引入了一个间接级别使用 std::index_sequence
:
创建它们
template<class... T>
void ProcessData(const std::tuple<T...>& data){
std::cout << "Processing " << sizeof...(T) << " data elements...\n";
detail::ProcessDataImpl(data, std::make_index_sequence<sizeof...(T)>{});
}
detail::ProcessDataImpl
的定义将使用一种称为简单包扩展的技术。这是一个技巧,我们利用数组初始化为参数包中的每个元素调用一个函数。看起来有点奇怪,但请耐心等待:
template<class... T, size_t... I>
void ProcessDataImpl(const std::tuple<T...>& data, std::index_sequence<I...>){
using swallow = int[];
(void)swallow{0, (void(ProcessElement(std::get<I>(data))), 0)...};
}
这将为元组中的每个元素调用一个名为 ProcessElement
的函数。我们使用逗号运算符和 void
转换来确保该函数实际上不做任何事情,并且我们所有的操作都只是为了它们的副作用(调用我们的 ProcessElement
函数)。
我们的 ProcessElement
函数将使用另一个间接级别来传递参数以处理更复杂的类型,例如我们的字符数组。否则我们可以为我们需要的类型重载它:
template<class T>
struct ProcessElementImpl
{
static void apply(const T& element)
{
static_assert(sizeof(T) == 0, "No specialization created for T");
}
};
template<size_t N>
struct ProcessElementImpl<char[N]>
{
static void apply(const char(&arr)[N])
{
std::cout << "Process char array of size " << N << std::endl;
}
};
template<class T>
void ProcessElement(const T& element)
{
ProcessElementImpl<T>::apply(element);
}
void ProcessElement(long _element)
{
std::cout << "Process a long\n";
}
void ProcessElement(double _element)
{
std::cout << "Process a double\n";
}
请注意,我们为 long
和 double
重载了,但我们将它传递给 ProcessElementImpl
以用于我们的字符数组。这是必需的,因为我们不能部分特化模板函数,并且我们想要处理任意大小的数组。
基本 class 模板还包含一个 static_assert
,因此我们不得不编写用于导出数据类型的专门化。
终于可以这样称呼了:
int main()
{
MYSTRUCT0 struct0;
ProcessData(struct0.GetData());
MYSTRUCT1 struct1;
ProcessData(struct1.GetData());
return 0;
}
输出:
Processing 2 data elements...
Process char array of size 10
Process char array of size 70
Processing 4 data elements...
Process a long
Process a long
Process char array of size 6
Process a double
我正在构建自定义 BDD class 来为我的程序存储不同类型的数据(例如,long
、char*
、double
、...)。
为了存储数据,我需要每个 table 的结构,如下所示:
struct MYSTRUCT0
{
char variable0[10];
char variable1[70];
};
struct MYSTRUCT1
{
long variable0;
long variable1;
char variable2[6];
double variable3;
};
但是每次我需要一个新的 table 时,工作量很大,因为我需要编写一个函数来将每个 table 保存在一个文件中,读取它等等。更糟糕的是,它不是真正面向对象。
所以我的问题是,有没有办法 "browse" 结构来简化我的代码? 像这样:
for(int v=0; v<arraysize; v++)
for(int i=0; i<MYSTRUC0.length; i++)
{
if (MYSTRUCT.getvar(i).type == long)
DoSomethingForLong(myarray(v).getval(i));
if (MYSTRUCT.getvar(i).type == char*)
DoSomethingForCharPtr(myarray(v).getval(i));
}
我知道这样的代码可以直接在 C++ 中运行。我只是用它来说明我的意思。
下面的代码只是一个示例,说明如何创建自己的 "variable-type-aware" 结构,这可能是您想要的:
#include <vector>
enum MyTypes
{
LONG,
CHARPTR,
DOUBLE,
} myTypes;
struct MyStruct
{
MyStruct(long longVar)
{
variable.longVar = longVar;
whichType = LONG;
}
MyStruct(char* charPtr)
{
variable.charPtr = charPtr;
whichType = CHARPTR;
}
MyStruct(double var)
{
variable.var = var;
whichType = DOUBLE;
}
~MyStruct()
{
}
MyTypes whichType;
union {
long longVar;
char* charPtr;
double var;
} variable;
};
void DoSomethingForLong(MyStruct* doubleStruct)
{
/*Do something specific to long*/
};
void DoSomethingForCharPtr(MyStruct* doubleStruct)
{
/*Do something specific to char pointer*/
};
void DoSomethingForDouble(MyStruct* doubleStruct)
{
/*Do something specific to double*/
};
int main()
{
std::vector<MyStruct*> myVec;
// add a struct with long variable
long longVar = 2000000000;
MyStruct* myLongStruct = new MyStruct(longVar);
myVec.push_back(myLongStruct);
// add a struct with char pointer
char* charArray = new char[1000];
MyStruct* myCharPtrStruct = new MyStruct(charArray);
myVec.push_back(myCharPtrStruct);
// add a struct with double variable
double doubleVar = 200.200;
MyStruct* myDoubleStruct = new MyStruct(doubleVar);
myVec.push_back(myDoubleStruct);
for (int i = 0; i < myVec.size(); ++i)
{
MyStruct* tempStruct = myVec[i];
if (tempStruct->whichType == LONG)
{
DoSomethingForLong(tempStruct);
}
else if (tempStruct->whichType == CHARPTR)
{
DoSomethingForCharPtr(tempStruct);
}
else if (tempStruct->whichType == DOUBLE)
{
DoSomethingForDouble(tempStruct);
}
}
if (myLongStruct)
{
delete myLongStruct;
myLongStruct = nullptr;
}
if (myCharPtrStruct)
{
if (charArray)
{
delete[] charArray;
charArray = nullptr;
}
delete myCharPtrStruct;
myCharPtrStruct = nullptr;
}
if (myDoubleStruct)
{
delete myDoubleStruct;
myDoubleStruct = nullptr;
}
}
如果您不厌其烦地添加一个可以将数据成员导出为元组的成员函数,那么我们可以使用一些模板元编程来完成这项工作。
Live Demo (C++14)
一、改动:
struct MYSTRUCT0
{
char variable0[10];
char variable1[70];
std::tuple<char(&)[10], char(&)[70]> GetData()
{
return std::tie(variable0, variable1);
}
};
struct MYSTRUCT1
{
long variable0;
long variable1;
char variable2[6];
double variable3;
std::tuple<long&, long&, char(&)[6], double&> GetData()
{
return std::tie(variable0, variable1, variable2, variable3);
}
};
std::tie
will put references to these members into a tuple
.
元组的好处在于它将所有类型编码到我们可以利用的列表中。 (您可能可以编写宏来为您创建这些结构。)
从这里开始,策略是编写一个可以处理任何元组的函数。
由于我们通过调用 std::get<i>
来访问元组的元素,其中 i
是某个索引,我们需要一种方法来获取这些元素的索引,因此我们引入了一个间接级别使用 std::index_sequence
:
template<class... T>
void ProcessData(const std::tuple<T...>& data){
std::cout << "Processing " << sizeof...(T) << " data elements...\n";
detail::ProcessDataImpl(data, std::make_index_sequence<sizeof...(T)>{});
}
detail::ProcessDataImpl
的定义将使用一种称为简单包扩展的技术。这是一个技巧,我们利用数组初始化为参数包中的每个元素调用一个函数。看起来有点奇怪,但请耐心等待:
template<class... T, size_t... I>
void ProcessDataImpl(const std::tuple<T...>& data, std::index_sequence<I...>){
using swallow = int[];
(void)swallow{0, (void(ProcessElement(std::get<I>(data))), 0)...};
}
这将为元组中的每个元素调用一个名为 ProcessElement
的函数。我们使用逗号运算符和 void
转换来确保该函数实际上不做任何事情,并且我们所有的操作都只是为了它们的副作用(调用我们的 ProcessElement
函数)。
我们的 ProcessElement
函数将使用另一个间接级别来传递参数以处理更复杂的类型,例如我们的字符数组。否则我们可以为我们需要的类型重载它:
template<class T>
struct ProcessElementImpl
{
static void apply(const T& element)
{
static_assert(sizeof(T) == 0, "No specialization created for T");
}
};
template<size_t N>
struct ProcessElementImpl<char[N]>
{
static void apply(const char(&arr)[N])
{
std::cout << "Process char array of size " << N << std::endl;
}
};
template<class T>
void ProcessElement(const T& element)
{
ProcessElementImpl<T>::apply(element);
}
void ProcessElement(long _element)
{
std::cout << "Process a long\n";
}
void ProcessElement(double _element)
{
std::cout << "Process a double\n";
}
请注意,我们为 long
和 double
重载了,但我们将它传递给 ProcessElementImpl
以用于我们的字符数组。这是必需的,因为我们不能部分特化模板函数,并且我们想要处理任意大小的数组。
基本 class 模板还包含一个 static_assert
,因此我们不得不编写用于导出数据类型的专门化。
终于可以这样称呼了:
int main()
{
MYSTRUCT0 struct0;
ProcessData(struct0.GetData());
MYSTRUCT1 struct1;
ProcessData(struct1.GetData());
return 0;
}
输出:
Processing 2 data elements...
Process char array of size 10
Process char array of size 70
Processing 4 data elements...
Process a long
Process a long
Process char array of size 6
Process a double