I try to make a class that will describe a certain table in the database. I just can't figure out how to best describe it. Now there is something like this:

class IColumn{ public: virtual const QString& name() const = 0; virtual const QString& type() const = 0; virtual const QString& settings() const = 0; virtual ~IColumn(){} }; class IRow{ public: virtual QString value(int index) const = 0; virtual int size() const = 0; virtual ~IRow(){} }; class ITable{ public: virtual int rowCount() const = 0; virtual int columnCount() const = 0; virtual const IRow& row(int index) const = 0; virtual const IColumn& column(int index) const = 0; virtual const QString& name() const = 0; virtual const QString& settings() const = 0; virtual ~ITable(){} }; 

A specific table should store a set of specific columns and specific rows:

 class Table : public ITable{ class Row : public IRow{ //... }; class Column : public IColumn{ //... }; QVector<Row> _rows; QVector<Column> _columns; //... }; 

In this organization, I am confused by the fact that the implementation of Row and the number of objects in the _columns vector _columns closely related, but this is not reflected in the code in any way. I can add a new column in _columns , but at the same time forget to change the implementation of Row and everything will compile normally.

It also looks ugly that ITable::columnCount and IRow::size should actually return the same value. But if you get rid of one of these methods, it will be impossible to find out how many columns are in an empty table or how many columns are in an arbitrary row.

Can someone suggest a more natural implementation of the table?

    1 answer 1

    In the table, rows and columns, in theory, should not store information. They are just a navigation tool. The data must be stored in a different entity called a cell.

    The table is a specialization of the matrix, which differs from the latter only by the presence of horizontal and vertical headers, so the implementation of the code I propose to start with the matrix.

     template<class T> class Matrix { public: explicit Matrix() : _rows(0), _cols(0) {} explicit Matrix(int rows, int cols) : _rows(rows), _cols(cols), _cells(rows*cols) {} virtual ~Matrix() {} int rows() const {return _rows;} int cols() const {return _cols;} void insertRows(int row, int cnt) { if(_rows >= row && row >= 0 && cnt > 0) { if(_cols == 0) _cols = 1; _cells.insert(_cols * row, _cols * cnt, T()); _rows += cnt; } } void removeRows(int row, int cnt) { if(_rows >= (row+cnt) && row >= 0 && cnt > 0) { _cells.remove(_cols * row, _cols * cnt); _rows -= cnt; if(_rows == 0) _cols = 0; } } void insertCols(int col, int cnt) { if(_cols >= col && col >= 0 && cnt > 0) { if(_rows == 0) _rows = 1; for(int row = 0; row < _rows; ++row) _cells.insert(pos(row,col)+(row*cnt), cnt, T()); _cols += cnt; } } void removeCols(int col, int cnt) { if(_cols >= (col+cnt) && col >= 0 && cnt > 0) { for(int row = 0; row < _rows; ++row) _cells.remove(pos(row,col)-(row*cnt), cnt); _cols -= cnt; if(_cols == 0) _rows = 0; } } const T &data(int row, int col) const { return _cells[pos(row,col)]; } T &data(int row, int col) { return _cells[pos(row,col)]; } void clear() {_rows = 0; _cols = 0; _cells.clear();} private: int _rows, _cols; QVector<T> _cells; int pos(int row, int col) const { return _cols * row + col; } }; 

    Someone prefers to store cells in a two-dimensional array, but I gave the option using a regular vector. Despite the greater difficulty in adding / deleting rows / columns compared to the two-dimensional option, this one is no less interesting.

    Since the table, as already stated, is a specialization of the matrix, the corresponding class can be easily inherited from the previous one with the addition of functionality for headings.

     template<class T> class Table : public Matrix<T> { public: explicit Table() : Matrix<T>(1,1) {} explicit Table(int rows, int cols) : Matrix<T>(rows+1,cols+1) {} virtual ~Table() {} int rows() const {return Matrix<T>::rows()-1;} int cols() const {return Matrix<T>::cols()-1;} void insertRows(int row, int cnt) { Matrix<T>::insertRows(row+1, cnt); } void removeRows(int row, int cnt) { Matrix<T>::removeRows(row+1, cnt); } void insertCols(int col, int cnt) { Matrix<T>::insertCols(col+1, cnt); } void removeCols(int col, int cnt) { Matrix<T>::removeCols(col+1, cnt); } const T &headerRowData(int row) const { return Matrix<T>::data(row+1, 0); } T &headerRowData(int row) { return Matrix<T>::data(row+1, 0); } const T &headerColData(int col) const { return Matrix<T>::data(0, col+1); } T &headerColData(int col) { return Matrix<T>::data(0, col+1); } const T &data(int row, int col) const { return AMatrix<T>::data(row+1, col+1); } T &data(int row, int col) { return Matrix<T>::data(row+1, col+1); } }; 

    As is well seen, the class of the table is not engaged in anything other than adjusting the indexes of rows and columns when referring to the methods of the matrix in order to be able to store information about the headers in its first row and in its first column.

    • Thanks for the detailed answer. As I understand it, the matrix contains elements of one type. And I have a jumble of strings, ints and bulls there. You can, of course, store everything in QVariant , but I sincerely believe that you can do without it - yrHeTaTeJlb
    • By the way, we can assume that the number of columns and their types are known in advance and never change. Therefore, over their removal / addition can not bother. - yrHeTaTeJlb
    • If you want to use your table class in conjunction with Qt models, then the latest QVariant actually a standard. With regards to supporting changes in the number of rows and columns, it is easy to remove from the class. - alexis031182
    • Regarding support for inserting and retrieving data of a certain type (string, number, etc.) without QVariant ... You will still need some kind of container class on templates that will store an arbitrary type (plus, probably this and interface classes will be needed). But in essence, the same "option." I'm just curious, for what reason do you refuse to use QVariant ? - alexis031182
    • I think not required. See what a story. Now there is a set of tables that sets the configuration of the application. Need to write a graphic editor for them. The application itself will operate with specific classes (not via interfaces). Interfaces are needed in order to later export these classes into sql-tables. - yrHeTaTeJlb