1
votes

I need to make multiple instances of various class types (classA, classB, classC), and store then in a single list. Is is possible to create these objects dynamically and then add them to a single QList? Something like:

QList<QObject> mylist;
mylist.append(classA());
mylist.append(classB());
mylist.append(classC());

This won't compile because the objects are not of type QObject. I could static_cast them to QObject, but will I be able to typeid.name() to determine their origical types? (To cast them back later)?

I found an example here of using a QList< QVariant >, but the compiler complains that:

error: no matching function for call to 'QVariant::QVariant(classA&)'

for this code:

QList<QVariant> mylist;
mylist.append(QVariant(tempObj));

Update:

I got it compiling by creating the object during the append operation, AND using ::fromValue.

mylist.append(QVariant::fromValue(ClassA(1,2,3)));

Not sure this is right. 1 - will this cause a memory leak/problem creating the object in the append statement? 2 - why would I need ::fromValue to make this work? (Does this just make a second copy of the object?)

I put a qDebug in my constructors, and I see that my constructor with parameters is called twice, then the copy constructor is called once, and the default (no parameters) constructor is never called. I don't understand that...

2
Why not just use a standard container such as std::list<QObject*> or std::list<std::unique_ptr<Object>>, since the objects are not QObjects? - PaulMcKenzie
I see no ways for a possible memory leak. QList will take ownership of the object. - It's Your App LLC
You need to use fromValue because the compiler will fail to find an overload of QList::append that takes a ClassA. - It's Your App LLC
Yeah, this is case where you want c++11 move semantics to cut down on the unnecessary copies. I am not sure but I don't think Qt is doing any move operations here. The code looks right. - It's Your App LLC
You are not using the heap (there are no new calls). You are allocating objects on the stack. They are unnamed meaning they are temporaries that "disappear" as soon as the statement finishes. ClassA constructor gets called once when you invoke it with ClassA(1,2,3). That returns a temporary that fromValue makes a copy of. That puts us at 1 constructor and 1 copy constructor. I am not sure what QList does but presumably its responsible for that final call to constructor that it makes when it "owns" the object. If you have move semantics you don't have to make so many unnecessary copies. - It's Your App LLC

2 Answers

3
votes

You can use QVariant which can hold any number of different types: QVariant API

To use QVariant with a custom type you need to register it with the metatype system: QMetaType This means using Q_DECLARE_METATYPE(MyClass) Q_DECLARE_METATYPE()

QVariant is designed for holding objects of different types, and is used in all sorts of Qt conventions when the programmer wants to pass around user-defined types through Qt's interfaces when Qt itself has no way of knowing which type the programmer wants to use.

To convert back to the original type use a combination of QVariant::canConvert() and QVariant::value():

QVariant v;

MyCustomStruct c;
if (v.canConvert<MyCustomStruct>())
    c = v.value<MyCustomStruct>();

v = 7;
int i = v.value<int>();                        // same as v.toInt()
QString s = v.value<QString>();                // same as v.toString(), s is now "7"
MyCustomStruct c2 = v.value<MyCustomStruct>(); // conversion failed, c2 is empty

Your custom type must provide:

  1. a public default constructor,
  2. a public copy constructor, and
  3. a public destructor.

The following Message class is an example of an acceptable custom type for use in QVariant

class Message
{
 public:
   Message();
   Message(const Message &other);
   ~Message();

   Message(const QString &body, const QStringList &headers);

   QString body() const;
   QStringList headers() const;

 private:
   QString m_body;
   QStringList m_headers;
};

You could also declare your own "meta-type wrapper" that holds a type and a description of a type. The simplest example:

template <typename T>
class AnyType
{
public:
  enum THE_TYPE { MT_INT, MT_STRING };

  AnyType(T value, THE_TYPE type) : value(value), type(type) { }
  THE_TYPE getType() { return type; }
  T getValue() { return value; }

private:
  T value;
  THE_TYPE type;
}
1
votes

I could static_cast them to QObject

No, you couldn't! And this is illegal:

QList<QObject> mylist;

QObjects have a notion of identity, and are non-copyable types. You have to manipulate them by pointer, and that would be a list of QObject values.

But speaking more generally (about seeking to get around this kind of thing with casting)...a QList and other templated containers specifically allocate an amount of memory for the object type they are parameterized with. So a QList<A> at the implementation level has only enough storage per item to store sizeof(A), while a QList<B> can only hold items that are sizeof(B).

So first of all: if you cast an object vs. casting a pointer then you really do lose information you can't get back...if you can even get the compiler to let you do it. And secondly: you need to be careful even if you have a slot that holds pointers to cast to them to a "punned type" and back again due to various pitfalls which come from doing so. Despite pointers to different types being the same size as pointers, you've got to reckon with things like the Strict Aliasing Requirement.

You can work around this with something like the QVariant as suggested by @snowandotherjoys. If you wanted to be more "standard" you might look at boost::variant.

But it's often said that a case requiring some kind of run-time-typing like this may mean you need to take a second look at the design. If classA and classB and classC are so different that they have no interface in common, what is it that makes putting them in a single collection that is compelling? What can you usefully do with a collection of things that have nothing in common? While these cases do come up (and motivate the existence of variants) it's valuable to step back and ask if you really do have one of those cases.