我正在尝试在任意结构的连续数组(我们称其为DataItem)的连续数组之上创建轻量级的,它将处理诸如文件IO,在屏幕/ GUI上呈现(如excel表),搜索和通过difernet属性等进行排序

但是我想让自己的class Table和用户定义的struct / class 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];
            }
        }
    }

最佳答案

解决这类问题的一种方法是提供一个“traits”类,该类告诉一个类如何处理另一个类而不必修改第二个类。此模式在标准库中广泛使用。

您的代码可以写为:

#include <iostream>
#include <string>
#include <vector>
#include <array>

template <typename T>
struct TableTraits;

template <typename T>
class Table
{
public:
  void setData( const std::vector<T>& value )
  {
    data = value;
  }

  std::string toString( size_t row, size_t column )
  {
    return TableTraits<T>::toString( data[ row ], column );
  }

  void print()
  {
    for ( size_t row = 0; row < data.size(); row++ )
    {
      for ( size_t column = 0; column < TableTraits<T>::columns; column++ )
      {
        std::cout << toString( row, column ) << ", ";
      }
      std::cout << "\n";
    }
  }
private:
  std::vector<T> data;
};

struct TestStruct
{
  int    inum = 115;
  double dnum = 11.1154546;
  double fvoid = 0.0;
  float  fnum = 11.115f;
  std::array<double, 3> dvec = { 1.1545, 2.166, 3.1545 };
};

template <typename T>
std::string stringConvert( const T& value )
{
  return std::to_string( value );
}

template <typename T, size_t N>
std::string stringConvert( const std::array<T, N>& value )
{
  std::string result;
  for ( auto& v : value )
  {
    result += stringConvert( v ) + "; ";
  }
  return result;
}

template <>
struct TableTraits<TestStruct>
{
  static const size_t columns = 5;

  static std::string toString( const TestStruct& row, size_t column )
  {
    switch ( column )
    {
    case 0:
      return stringConvert( row.inum );
    case 1:
      return stringConvert( row.dnum );
    case 2:
      return stringConvert( row.fvoid );
    case 3:
      return stringConvert( row.fnum );
    case 4:
      return stringConvert( row.dvec );
    default:
      throw std::invalid_argument( "column out of range" );
    }
  }
};

int main()
{
  std::vector<TestStruct> data( 10 );
  Table<TestStruct> table;
  table.setData( data );
  table.print();
}

如果此示例不能完全满足您的需求,那么traits类的确切详细信息可能会有所不同。

您可能还会发现使traits方法和常量非静态很有用,这样您就可以将traits对象传递到表中以允许对每个表实例进行自定义。

您可能还希望允许在表中使用自定义特征类,如下所示:
template <typename T, typename Traits = TableTraits<T>>
class Table
{
...
  std::string toString( size_t row, size_t column )
  {
    return Traits::toString( data[ row ], column );
  }

07-24 09:52
查看更多