QList has no virtual destructor, but is inherited from



  • (4.7.4)

    This sounds dangerous. If I were to delete the base class pointer only, some cleanup might get missed.

    The official derived classes (QItemSelection, QQueue, QSignalSpy, QStringList and QTestEventList) either don't do anything in their destructors, or nothing much. Looks harmless that way. But the fact that several classes derive from QList indicates it is fit to be used as a base class. Then add your own code to a destructor, and you are in trouble.



  • The initial reason for not having a virtual QList destructor in Qt4 was binary compatibility. It was discussed for Qt5, but it has been rejected as it would introduce - with QList beeing a copyable type - another problem, slicing.

    So the actual problem is that all those classes derive from QList, not QList having no virtual destructor. But as we have source-code compatibilty for Qt5 this is a problem that will have to wait for Qt6.

    The only solution so far is beeing aware of this pitfall - which should be most probably mentioned in the documentation as well.



  • I added a note to the public docs entry of QList



  • Regarding the doc note:

    bq. If you need to add actual code to your derived class destructor, you are probably better off not inheriting from QList at all.

    This only holds true, if you access the destructor via a QList base pointer! As long as you delete a derived class pointer or use your subclass as a value type (stack allocation) everything works fine.

    You might want to add this to the note.



  • This is especially relevant, because QList is implicitly shared, and thus can be used as a value-class. That means that you'll usually pass QList<YourType> instead of QList<YourType>* to methods.



  • You are of course correct, but I find it dangerous to assume, at the time I create a class, how it's going to be used in the end. If I later on decide I need a base class pointer for some reason, I should of course read up in the documentation of the class whether I am even allowed to do that. But altogether, it's a potential source of (hard-to-find) errors.



  • I have re-worded the note somewhat.



  • [quote author="Andre" date="1334679616"]This is especially relevant, because QList is implicitly shared, and thus can be used as a value-class. That means that you'll usually pass QList<YourType> instead of QList<YourType>* to methods. [/quote]

    Let's say I have a class MyDerivedList based on QList. MyDerivedList needs a copy constructor.

    I get a MyDerivedList<MyType>, but store it as a QList<MyType>. When I cause a deep-copy now (e.g. by changing data), which copy constructor(s) will be called?

    Edit:

    Answering my own question: It wouldn't do the "right thing" regardless of whether I used QList<MyType> or QList*<MyType>. A way to get polymorphism to work in this case would be the virtual constructor idiom, which however has to be supported in the base class.

    Edit 2:

    Sorry



  • Please stop replying to yourself (if you're the last one who posted in the topic, at least). Instead, just edit your last message.



  • [quote author="Asperamanca" date="1334734252"][quote author="Andre" date="1334679616"]This is especially relevant, because QList is implicitly shared, and thus can be used as a value-class. That means that you'll usually pass QList<YourType> instead of QList<YourType>* to methods. [/quote]

    Let's say I have a class MyDerivedList based on QList. MyDerivedList needs a copy constructor.

    I get a MyDerivedList<MyType>, but store it as a QList<MyType>. When I cause a deep-copy now (e.g. by changing data), which copy constructor(s) will be called?
    [/quote]

    With the initial copy of the list, the copy constructor of QList would be used (or the assignment operator, of course). During the deep-copy, your items are copied, not the list object itself. So, the copy constructors of your MyType will be called, it seems to me.



  • Of course, the copy constructor for each item of the list would be called (unless they were movable types).

    But in addition, the copy constructor of the whole container would be called, no? If, for example, I were to do some bookkeeping in MyDerivedList, data that needs to be copied, I might be in trouble. But as I wrote before, it doesn't matter whether I use a pointer to the list or not.

    So I was beside the point (I was originally trying to make).



  • The MyDerivedList list is already copied. That is a trivial operation, by the way. What happens, is that the internal private data storage, which is intially shared after the original copy, is copied (with all the objects inside it) as well. However, as this private list is an implementation detail of QList, and something that is beyond your reach to change by subclassing, it should not matter to you how this is done.

    Of course, if you write a copy constructor or assignment operator in your derived class, you must make sure you call the base class implementation as well! However, that is a general advice for any derived class. See Item 12 from Scott Meyers Effective C++ (3rd edition). And yes, I realize that not having the destructor virtual while we have inherited from QList is probably a violation of Item 7 of that same book, at least if any of these classes add any resources to the class.



  • Right, I haven't described the copy process correctly. Of course, the QList itself is copied the moment it is...er...copied, or assigned. Only the content of the data capsule is copied at a later time (copy-on-write).

    You bring up another interesting point: I can ruin some of the advantages of an implicitly shared class by deriving from it, and adding data members. Since I have no way to expand the private data capsule, I can either add the member variables plainly, or create another data capsule for implicit sharing. I hadn't considered those implications so far...

    However, I feel you still missed my point regarding copy constructors. I'll sum it up in one sentence, and leave it at that:

    "A base class cannot know it has to call the copy constructor of a derived class."

    That a derived class has to call the copy constructor of its base class is clear.



  • [quote author="Asperamanca" date="1334737610"]
    However, I feel you still missed my point regarding copy constructors. I'll sum it up in one sentence, and leave it at that:

    "A base class cannot know it has to call the copy constructor of a derived class."

    That a derived class has to call the copy constructor of its base class is clear.[/quote]

    Well, but that is a moot point, isn't it? It would be impossible for any class to copy into it the members of a derived class. So, a base class doesn't need to know how to call the copy constructor of derived classes. And yes, that means that you have trouble with polymorphism, in that you can't clone a derived class using the copy constructor or assignment operator if you just have a base class pointer. Meyers suggest creating a virtual clone() method for that that returns a pointer instead of a reference, but you obviously can't do that for QList.

    [quote author="Asperamanca" date="1334734252"]I get a MyDerivedList<MyType>, but store it as a QList<MyType>. When I cause a deep-copy now (e.g. by changing data), which copy constructor(s) will be called?

    Edit:

    Answering my own question: It wouldn't do the "right thing" regardless of whether I used QList<MyType> or QList*<MyType>. A way to get polymorphism to work in this case would be the virtual constructor idiom, which however has to be supported in the base class.[/quote]
    Indeed: the virtual clone method. Note that it's not even possible to do without using pointers to the lists either. You cannot assign a MyDerivedList<MyType> to a QList<MyType> at all. Polymorphism needs pointers or references to work its magic, right?


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.