Type Erasure In QModelIndex
-
I create my custom model by QAbstractItemModel. The Qt model use QModelIndex just with row, column and a void* pointer as abstrauct index to actual data. it works well when the actual all data struct have same base virtual class.
But I meet a problem that my actual data structs have different base class. I can not modify the data struct code, they from other module.
If QModelIndex can replace internal pointer from void* to QVariant that I can pass the struct after type erasure. I try change Qt source code and compile, it work well.
Can Qt accpet this change or provade other to QModelIndex so user can identify what actual type internal pointer is. I think its usefulthank for your read
-
This change will not be accepted and it's not needed at all. There is no difference between storing a pointer as void* or in a QVariant.
-
I create my custom model by QAbstractItemModel. The Qt model use QModelIndex just with row, column and a void* pointer as abstrauct index to actual data. it works well when the actual all data struct have same base virtual class.
But I meet a problem that my actual data structs have different base class. I can not modify the data struct code, they from other module.
If QModelIndex can replace internal pointer from void* to QVariant that I can pass the struct after type erasure. I try change Qt source code and compile, it work well.
Can Qt accpet this change or provade other to QModelIndex so user can identify what actual type internal pointer is. I think its usefulthank for your read
... or using an abstract indexing of
quintptr
type ... you can save references to your objects however you desire and provide the indexing through the overload that takes an integer type. -
This change will not be accepted and it's not needed at all. There is no difference between storing a pointer as void* or in a QVariant.
@Christian-Ehrlicher a void* pinter delete type info, If pass the struct pointer after type erasure, It can not find a suitable time to release passed struct pointer.
class File { const char* GetData() const ... } class Folder { const char* GetName() const ... std::set<File*> fileset; std::set<Folder*> folder; }
just like above, wo want to show the folder file like real file struct. unfortunately, only a void* can not identify it is a folder or a file because they have different base class. and there are many time the data and the hierarchies is from other module so wo can change it. but if use QVariant we can add type info in to QVariant.
actually I meet this problem in my work, void* pointer can not identify what actual type is but QVariant can. I have to wrtite ugly and complex code to show this hierarchies
I think this change is tiny and have no bad effect for dev and runtime efficiency, It can improve QAbstrauctItemModel`s compatibility -
Then derive both from a common virtual base class.
-
@Christian-Ehrlicher a void* pinter delete type info, If pass the struct pointer after type erasure, It can not find a suitable time to release passed struct pointer.
class File { const char* GetData() const ... } class Folder { const char* GetName() const ... std::set<File*> fileset; std::set<Folder*> folder; }
just like above, wo want to show the folder file like real file struct. unfortunately, only a void* can not identify it is a folder or a file because they have different base class. and there are many time the data and the hierarchies is from other module so wo can change it. but if use QVariant we can add type info in to QVariant.
actually I meet this problem in my work, void* pointer can not identify what actual type is but QVariant can. I have to wrtite ugly and complex code to show this hierarchies
I think this change is tiny and have no bad effect for dev and runtime efficiency, It can improve QAbstrauctItemModel`s compatibilityAny particular reason for you not to leverage runtime polymorphism and use
dynamic_cast
? -
Any particular reason for you not to leverage runtime polymorphism and use
dynamic_cast
?@kshegunov said in Type Erasure In QModelIndex:
Any particular reason for you not to leverage runtime polymorphism and use
dynamic_cast
?actually i use
dynamic_cast
to solve thi problem for now, but there are too many problem with virtual inherit base class, multi inherit and diamond inherit. and i can NOT modify data struct hierarchies because it not write by me. Before usedynamic_cast
you also have to change void* to a virtual class pointer but some time the real type is not a virtual class -
Hi,
Can you show how you implemented your model ? It looks like you may be taking the problem from the wrong end.
-
Hi,
Can you show how you implemented your model ? It looks like you may be taking the problem from the wrong end.
@SGaist said in Type Erasure In QModelIndex:
wrong end
the data structure and hierarchy like below
THIS DATA STRUCTURE DEFINE IN OUTER MODULE LIKE DYNMAIC LIBRARY AND MAINTAIN BY OUTER,
NO WAY TO MODIFY OR OPERATE
root(Group*) | |---props--|--prop | |--prop | |--prop | |-- ... | |---group--|--props--|--prop | | |--prop | | |-- ... | | | |--item------props--|--prop | |--item---+ |--prop | |--item---+ |--prop | |--prop |---group--+ |-- ... |---group--+ |--- ... |---item---|--props--|--prop |---item |--prop |---item |--prop |---item |--- ... class Prop // base class { (some property info) }; class Item // base class, can inherited by other class but not Prop or Group { (some data) std::vector<Prop> props; }; class Group // base class { std::vector<Prop> props; std::set<Group*> groups; std::set<Item*> items };
my model implement
// its hard to implement with 2 num 1 void* and above data structure bool IsGroup(const ModelIndex&) { ... } bool IsProps(const ModelIndex&) { ... } QModelIndex MyModel::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); if ( IsGroup(parent) ) { // return a props pointer or Group* or Item* ... } else if( IsProps(parent) ) { // return pointer in std::vector data(), not safe ... } else // parentitem is Item* { // return props pointer } return QModelIndex(); } QModelIndex MyModel::parent(const QModelIndex& index) const { // is hard to implement if actual data struct's inclusion relation is one-way just with a row num, a column num and a void * // so i traversal from root to find what it real parent } int MyModel::rowCount(const QModelIndex& parent) const { if (parent.column() > 0) return 0; if ( IsGroup(parentItem) ) { void* parentItem = parent.isValid() ? parent.internalPointer() : root; Group* g = static_cast<Group*>(parentItem); return 1 + g->groups.size() + g->items.size(); } else if( IsProps(parentItem) ) { auto props =static_cast<std::vector<Prop>* >(parent.internalPointer()); return props->size(); } return 1; }
Its not complete code, but I think its clearly.
I want to pass some associate info to QModelIndex, and it would release when QModelIndex release, void* can not do this. QVariant or share_ptr... can do this and not destory original Qt design.
Privade other solution is also welcome, I just want to solute this problem elegantly
Thanks for your help -
@kshegunov said in Type Erasure In QModelIndex:
Any particular reason for you not to leverage runtime polymorphism and use
dynamic_cast
?actually i use
dynamic_cast
to solve thi problem for now, but there are too many problem with virtual inherit base class, multi inherit and diamond inherit. and i can NOT modify data struct hierarchies because it not write by me. Before usedynamic_cast
you also have to change void* to a virtual class pointer but some time the real type is not a virtual class@crow said in Type Erasure In QModelIndex:
actually i use dynamic_cast to solve thi problem for now, but there are too many problem with virtual inherit base class, multi inherit and diamond inherit. and i can NOT modify data struct hierarchies because it not write by me. Before use dynamic_cast you also have to change void* to a virtual class pointer but some time the real type is not a virtual class
One thing that comes to mind is the Curiously Recurring Template Pattern (CRTP). It is not the most elegant solution, but I guess it helps when you cannot change the original classes because they are part of a library.
Let's assume you have some library classes:
class SomeLibClassA; class SomeLibClassB; ... // maybe more classes
The
dynamic_cast
needs to be replaced with switch over enums later on. So, define your enum:enum class ClassType { UNKNOWN, SOME_LIB_CLASS_A, SOME_LIB_CLASS_B, ...}; // add enums for all your classes here
For convenience I suggest a comman base class for all CRTP classes:
class Base { public: ClassType type = ClassType::UNKNOWN; };
And now for the CRTP template:
template <class T> class CRTPBase : public Base { public: T *object; };
This can now be used to create wrapper classes for the library classes:
class SomeLibClassAWrapper : public CRTPBase<SomeLibClassA> { public: SomeLibClassAWrapper(SomeLibClassA *o) : type(ClassType::SOME_LIB_CLASS_A), object(o) {} }; class SomeLibClassBWrapper : public CRTPBase<SomeLibClassB> { public: SomeLibClassBWrapper(SomeLibClassB *o) : type(ClassType::SOME_LIB_CLASS_B), object(o) {} }; ... // further lib class wrappers
Now you can use
Base*
instead ofvoid*
orQVariant
.SomeLibClassA *a = new SomeLibClassA(...); // somewhere you create your object ... Base *object = new SomeLibClassAWrapper(a); // you wrap it for your QAbstractItemModel ... switch(object->type) { case ClassType::SOME_LIB_CLASS_A: { SomeLibClassA *a = static_cast<SomeLibClassAWrapper*>(object)->object; ... // do your stuff } break; case ClassType::SOME_LIB_CLASS_B: { SomeLibClassB *b = static_cast<SomeLibClassBWrapper*>(object)->object; ... // do your stuff } break; case ...: // other lib classes }
You see that now you can even use
static_cast
instead ofdynamic_cast
to avoid some additional runtime overhead.You also see that you have to be careful to change a few places when adding another library class you need to use. 1) There's the enum, 2) the wrapper class declaration, and 3) the switch statement (maybe switch statements in multiple places). This makes this solution not elegant in the general case. You have to weigh for yourself if this is a proper solution under the restrictions you have.
-
@crow said in Type Erasure In QModelIndex:
actually i use dynamic_cast to solve thi problem for now, but there are too many problem with virtual inherit base class, multi inherit and diamond inherit. and i can NOT modify data struct hierarchies because it not write by me. Before use dynamic_cast you also have to change void* to a virtual class pointer but some time the real type is not a virtual class
One thing that comes to mind is the Curiously Recurring Template Pattern (CRTP). It is not the most elegant solution, but I guess it helps when you cannot change the original classes because they are part of a library.
Let's assume you have some library classes:
class SomeLibClassA; class SomeLibClassB; ... // maybe more classes
The
dynamic_cast
needs to be replaced with switch over enums later on. So, define your enum:enum class ClassType { UNKNOWN, SOME_LIB_CLASS_A, SOME_LIB_CLASS_B, ...}; // add enums for all your classes here
For convenience I suggest a comman base class for all CRTP classes:
class Base { public: ClassType type = ClassType::UNKNOWN; };
And now for the CRTP template:
template <class T> class CRTPBase : public Base { public: T *object; };
This can now be used to create wrapper classes for the library classes:
class SomeLibClassAWrapper : public CRTPBase<SomeLibClassA> { public: SomeLibClassAWrapper(SomeLibClassA *o) : type(ClassType::SOME_LIB_CLASS_A), object(o) {} }; class SomeLibClassBWrapper : public CRTPBase<SomeLibClassB> { public: SomeLibClassBWrapper(SomeLibClassB *o) : type(ClassType::SOME_LIB_CLASS_B), object(o) {} }; ... // further lib class wrappers
Now you can use
Base*
instead ofvoid*
orQVariant
.SomeLibClassA *a = new SomeLibClassA(...); // somewhere you create your object ... Base *object = new SomeLibClassAWrapper(a); // you wrap it for your QAbstractItemModel ... switch(object->type) { case ClassType::SOME_LIB_CLASS_A: { SomeLibClassA *a = static_cast<SomeLibClassAWrapper*>(object)->object; ... // do your stuff } break; case ClassType::SOME_LIB_CLASS_B: { SomeLibClassB *b = static_cast<SomeLibClassBWrapper*>(object)->object; ... // do your stuff } break; case ...: // other lib classes }
You see that now you can even use
static_cast
instead ofdynamic_cast
to avoid some additional runtime overhead.You also see that you have to be careful to change a few places when adding another library class you need to use. 1) There's the enum, 2) the wrapper class declaration, and 3) the switch statement (maybe switch statements in multiple places). This makes this solution not elegant in the general case. You have to weigh for yourself if this is a proper solution under the restrictions you have.
@SimonSchroeder said in Type Erasure In QModelIndex:
Curiously Recurring Template Pattern
thank for your reply.
i think this mothod is same to Type Erasure. It work on many scene, but not QModelIndex.
Let me show you why.- before create a QModelIndex, we warp a class pointer in a new Class
- we pass new pointer with type info into QModelIndex as a void*
- now we can get right type info
- the question is, how to release this NEW WARP Class pointer? When? If we use a QVatiant, wo can pass object not a pointer would release auto when QModelIndex release
-
@SimonSchroeder said in Type Erasure In QModelIndex:
Curiously Recurring Template Pattern
thank for your reply.
i think this mothod is same to Type Erasure. It work on many scene, but not QModelIndex.
Let me show you why.- before create a QModelIndex, we warp a class pointer in a new Class
- we pass new pointer with type info into QModelIndex as a void*
- now we can get right type info
- the question is, how to release this NEW WARP Class pointer? When? If we use a QVatiant, wo can pass object not a pointer would release auto when QModelIndex release
@crow said in Type Erasure In QModelIndex:
@SimonSchroeder said in Type Erasure In QModelIndex:
Curiously Recurring Template Pattern
thank for your reply.
i think this mothod is same to Type Erasure. It work on many scene, but not QModelIndex.
Let me show you why.
...
4. the question is, how to release this NEW WARP Class pointer? When? If we use a QVatiant, wo can pass object not a pointer would release auto when QModelIndex releaseQModelIndex is an index into a model. It is not the model.
Make the CRTP, polymorphic, QVariant, or whatever object a part of the model. This object's lifetime should that of the model or at least the item in the model. It may need to be part of a data structure that parallels the referenced code that can't be changed. This is also an opportunity to solve the parent tracing problem alluded to.
-
@crow said in Type Erasure In QModelIndex:
@SimonSchroeder said in Type Erasure In QModelIndex:
Curiously Recurring Template Pattern
thank for your reply.
i think this mothod is same to Type Erasure. It work on many scene, but not QModelIndex.
Let me show you why.
...
4. the question is, how to release this NEW WARP Class pointer? When? If we use a QVatiant, wo can pass object not a pointer would release auto when QModelIndex releaseQModelIndex is an index into a model. It is not the model.
Make the CRTP, polymorphic, QVariant, or whatever object a part of the model. This object's lifetime should that of the model or at least the item in the model. It may need to be part of a data structure that parallels the referenced code that can't be changed. This is also an opportunity to solve the parent tracing problem alluded to.
@jeremy_k said in Type Erasure In QModelIndex:
alluded
so this "Abstract" model just for static model. Image that you have a dynamic tree with thousands of items. As time goes by, thousands of items add to tree, and thousands of items remove. and the model hold item warp index object which have been romove from tree and never use again because model lifetime is at the end. the memory usage must be crazy.
finally, I solve this problem by create a new view/model framework which can handle more hierarchy type. it take time but i need it.
just a litte discussion even not a advice for Qt Framework. Whatever, my problem endThank for your patience
-
@jeremy_k said in Type Erasure In QModelIndex:
alluded
so this "Abstract" model just for static model. Image that you have a dynamic tree with thousands of items. As time goes by, thousands of items add to tree, and thousands of items remove. and the model hold item warp index object which have been romove from tree and never use again because model lifetime is at the end. the memory usage must be crazy.
finally, I solve this problem by create a new view/model framework which can handle more hierarchy type. it take time but i need it.
just a litte discussion even not a advice for Qt Framework. Whatever, my problem endThank for your patience
@crow said in Type Erasure In QModelIndex:
@jeremy_k said in Type Erasure In QModelIndex:
alluded
I don't know what quoting this word on its own means.
so this "Abstract" model just for static model. Image that you have a dynamic tree with thousands of items. As time goes by, thousands of items add to tree, and thousands of items remove.
This doesn't describe anything that QAbstractItemModel can't be used for. The efficiency of the underlying data structure and access patterns will have an impact on performance, but this is true for data structures in general.
and the model hold item warp index object which have been romove from tree and never use again because model lifetime is at the end. the memory usage must be crazy.
Why is storing the type for a mixed data tree's node in the tree more expensive that storing them separately? If that data is only stored in a temporary index, how is that index constructed in the first place?