我试图在任意结构的连续数组(称之为 DataItem
)的顶部制作轻量级层,该层将处理常见的操作,如文件IO、在屏幕/GUI上呈现(如Excel表格)、按不同属性搜索和排序等。
但是我希望我的 class Table
和用户定义的结构/类 DataItem
完全独立于彼此(即两者都可以编译而不知道彼此的头文件.h
)。我认为这不能像这样实现:template<class T> class Table{ std::vectro<T> data;};
,因为那么用户将被强制实现功能,如 DataItem::toString(int icolumn)
,而我不想对 DataItem 结构施加这种限制。
我的当前实现依赖于指针算术、switch,并且只能处理少量数据成员类型(bool、int、float、double)。我想知道是否可以通过使用模板等方式来改进它(使其更通用、安全等),而不会显著增加复杂性和性能成本。
我想这样使用它:
#include "Table.h"
#include "GUI.h"
#include "Vec3d.h"
// example of user defined DataItem struct
struct TestStruct{
int inum = 115;
double dnum = 11.1154546;
double fvoid= 0.0;
float fnum = 11.115;
Vec3d dvec = (Vec3d){ 1.1545, 2.166, 3.1545};
};
int main(){
// ==== Initialize test data
Table* tab1 = new Table();
tab1->n = 120;
TestStruct* tab_data = new TestStruct[tab1->n];
for(int i=0; i<tab1->n; i++){
tab_data[i].inum = i;
tab_data[i].fnum = i*0.1;
tab_data[i].dnum = i*0.01;
}
// ==== Bind selected properties/members of TestStruct as columns int the table
tab1->bind(tab_data, sizeof(*tab_data) );
// This is actually quite complicated =>
// I would be happy if it could be automatized by some template magic ;-)
tab1->addColum( &(tab_data->inum), 1, DataType::Int );
tab1->addColum( &(tab_data->fnum), 1, DataType::Float );
tab1->addColum( &(tab_data->dnum), 1, DataType::Double );
tab1->addColum( &(tab_data->dvec), 3, DataType::Double );
// ==== Visualize the table Table in GUI
gui.addPanel( new TableView( tab1, "tab1", 150.0, 250.0, 0, 0, 5, 3 ) );
gui.run();
}
我的当前实现看起来像这样:
enum class DataType{ Bool, Int, Float, Double, String };
struct Atribute{
int offset; // offset of data member from address of struct instance [bytes]
int nsub; // number of sub units. e.g. 3 for Vec3
DataType type; // type for conversion
Atribute() = default;
Atribute(int offset_,int nsub_,DataType type_):offset(offset_),nsub(nsub_),type(type_){};
};
class Table{ public:
int n; // number of items/lines in table
int itemsize = 0; // number of bytes per item
char* data = 0; // pointer to data buffer with structs; type is erased to make it generic
std::unordered_map<std::string,int> name2column;
std::vector <Atribute> columns;
void bind(void* data_, int itemsize_){
data=(char*)data_;
itemsize=itemsize_;
}
int addColum(void* ptr, int nsub, DataType type){
// determine offset of address of given data-member with respect to address of enclosing struct
int offset = ((char*)ptr)-((char*)data);
columns.push_back( Atribute( offset, nsub, type ) );
return columns.size()-1;
}
char* toStr(int i, int j, char* s){
const Atribute& kind = columns[j];
void* off = data+itemsize*i+kind.offset; // address of j-th member of i-th instance in data array
// I don't like this switch,
// but still it seems simpler and more efficient that alternative solutions using
// templates/lambda function or function pointers
switch(kind.type){
case DataType::Bool :{ bool* arr=(bool *)off; for(int i=0; i<kind.nsub; i++){ s+=sprintf(s,"%c ", arr[i]?'T':'F' ); }} break;
case DataType::Int :{ int* arr=(int *)off; for(int i=0; i<kind.nsub; i++){ s+=sprintf(s,"%i ", arr[i] ); }} break;
case DataType::Float :{ float* arr=(float *)off; for(int i=0; i<kind.nsub; i++){ s+=sprintf(s,"%g ", arr[i] ); }} break;
case DataType::Double :{ double* arr=(double*)off; for(int i=0; i<kind.nsub; i++){ s+=sprintf(s,"%g ", arr[i] ); }} break;
case DataType::String :{ char* arr=(char *)off; for(int i=0; i<kind.nsub; i++){ s+=sprintf(s,"%s ", arr[i] ); }} break;
}
return s;
}
};
// .... Ommited most of TableView GUI ....
void TableView::render(){
Draw ::setRGB( textColor );
char stmp[1024];
for(int i=i0; i<imax;i++){
int ch0 = 0;
for(int j=j0; j<jmax;j++){
int nch = table->toStr(i,j,stmp)-stmp; // HERE!!! I call Table::toStr()
Draw2D::drawText( stmp, nch, {xmin+ch0*fontSizeDef, ymax-(i-i0+1)*fontSizeDef*2}, 0.0, GUI_fontTex, fontSizeDef );
ch0+=nchs[j];
}
}
}
Table.cc
、Table.h
、DataItem.cc
、DataItem.h
,以及可能使用它们的prog.cc
,所有这些文件都应该是极简主义的,以满足您的需求。您所提供的内容只是这个的粗略代理。 - sancho.s ReinstateMonicaCellio