2

I am using Qt5 with the VS2013 compiler. I am trying to nest two classes CUDial and DUDial in the same outer class UDial.

UDial is a QDialog type class. I would like CUDial and DUDial to be both of QWidget type. It for use with a QTabWidget variable in the outer class.

So, I write the code as follow:

class UDial : public QDialog
{
    Q_OBJECT

    class CUDial : public QWidget
    {
        Q_OBJECT

        // Some variables

     public:

        CUDial(QWidget *parent = 0);
    }wid1;

   class DUDial : public QWidget
   {
        Q_OBJECT

        // Some variables

    public:

        DUDial(QWidget *parent = 0);
   }wid2;

   QTabWidget *tab;
   QDialogButtonBox *box;
   QVBoxLayout *vlay;

public:

   UDial(QWidget *parent = 0);
};

After I implemented the code, I tried to compiled and got the following C2664 error:

error: C2664: 'int QTabWidget::addTab(QWidget *,const QIcon &,const QString &)' : cannot convert argument 1 from 'UDial::CUDial' to 'QWidget *'

No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

I guess that it is a problem with the QWidget inheritance of my nested classes.

Is there a way I can solve that issue?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Patrik
  • 401
  • 1
  • 6
  • 23

4 Answers4

4

There are two problems:

  1. A trivial one: the lack of object dereference elsewhere in your code - a typo.

  2. The meta object compiler (moc) does not support nested classes. Your code will compile, but it won't pass the moc step, with the following error:

    Error: Meta object features not supported for nested classes

I do commend you for not prematurely pessimizing the storage of wid1 and wid2. You stored them by value - as you should. Ideally, though, you should store all such objects by value - not only the two sub-dials. It will also make things easier to read to decouple the instantiation of the inner classes from their declaration.

So, the sub-dial types need to be moved out of the class. You can put them in a namespace so that they don't pollute the global one.

namespace UDial_ {
class CUDial : public QWidget {
   Q_OBJECT
public:
   CUDial(QWidget *parent = 0) : QWidget(parent) {}
};

class DUDial : public QWidget
{
   Q_OBJECT
public:
   DUDial(QWidget *parent = 0) : QWidget(parent) {}
};
}

Secondly, once you do store objects by value, the children must be declared after the parents. This is because the compiler will generate code to destruct them in the reverse order of declaration. The child QObjects must be destroyed before the parents, otherwise the parents will, incorrectly, attempt to delete them.

I don't know of any way to force the compiler to enforce this without changing Qt source code, but you can at least indicate your intent by using the C++11 value initialization.

class UDial : public QDialog
{
   Q_OBJECT
   QVBoxLayout vlay { this }
   QTabWidget tab;
   // We make it explicit that both sub-dials are children of tab and must not
   // be declared / initialized before it!
   UDial_::CUDial wid1 { &tab };
   UDial_::DUDial wid2 { &tab };
   QDialogButtonBox box;

public:
   UDial(QWidget *parent = 0);
};

Unfortunately, the compiler will at best emit a warning if we do the wrong thing, although any decent static analyzer will loudly complain about it, of course.

class UDial : public QDialog
{
   Q_OBJECT
   QVBoxLayout vlay { this };
   UDial_::CUDial wid1 { &tab };
   UDial_::DUDial wid2 { &tab };
   QTabWidget tab; // WRONG, comes after the use above
   QDialogButtonBox box;

public:
   UDial(QWidget *parent = 0);
};

This code will, in most cases, crash upon startup, but that's not a crash we can depend on - say, as if we dereferenced a nullptr. But beware: the compiler is free to optimize out any dereferences of a nullptr!

The constructor will then look similar to:

UDial::UDial(QWidget * parent) : 
  QDialog(parent),
  box(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
{
  vlay.addWidget(&tab);
  vlay.addWidget(&box);
  tab.addTab(&wid1, "Dial 1");
  tab.addTab(&wid2, "Dial 2");
}

If you're using an older compiler that doesn't support C++11 value initialization, you cannot use the brace initialization syntax, and you are forced to declare your intent in the constructor - where it is more likely to be ignored by an unwitting maintainer:

UDial::UDial(QWidget * parent) : 
  QDialog(parent),
  vlay(this),
  wid1(&tab), wid2(&tab),
  box(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
{ ...

BartoszKP has a valid argument that since the checking of this cannot be reliably relegated to compile time, there is some merit to explicitly allocating all sub-objects on the heap, and letting Qt deal with proper destruction order at run-time. While I personally haven't run into any problems related to that, I'm very diligent and in a privileged position of having me and myself to maintain my code base. In larger projects that are not managed with proper process documentation in place to point out such procedural details, this approach may lead to errors. Such errors are, IMHO, more of an indication of a procedural issue in the development process than the validity of the approach. It then becomes a premature pessimization to work around development process deficiencies. It's up to you to decide whether that's a valid tradeoff. I personally cringe when I see the result of new assigned to a raw pointer - it tends to desensitize one to such incorrect uses where you don't have a nice memory-managing class like QObject.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • In general I agree with not using `new` wherever you can, but this: `// must come before sub-widgets` is scary. There is an implicit dependency between these objects that's hard to express in the code, otherwise then with a comment - that is a code smell. – BartoszKP Aug 20 '15 at 21:04
  • @BartoszKP That comment is only for the perusal of these who don't know that it matters :) It is possible to express such dependencies, of course. I've amended the answer. – Kuba hasn't forgotten Monica Aug 20 '15 at 21:11
  • Yes, I understand that it's only for the purposes of SO answer, however you don't have any other way to do it, apart from, as you correctly indicate, relying on a compiler's warning. And as you admit, only some compilers will produce such a warning. And still, maintaining the order of initialization to follow the evolution of your code is a nuisance, when you can push the work (as you propose yourself in the answer) to Qt's internals :) – BartoszKP Aug 20 '15 at 21:15
  • @BartoszKP OK, I guess it's a matter of style, but I have fairly large code bases where I do this and it has never been a problem in practice. I'm now wondering if there's some C++-fu that could be used to express such relationships to always enforce them at compile time while retaining the conciseness and performance of by-value member storage... – Kuba hasn't forgotten Monica Aug 20 '15 at 21:19
  • I'm guessing we have a completely different experience: I've also been working in large Qt projects, and by-value storage had led to some hard to debug problems in the past. This was legacy code though, if your team is disciplined enough (not to allow warnings, for example) I guess your approach is fine, and I agree it's a matter of style then :) – BartoszKP Aug 20 '15 at 21:27
  • Good note on moc - this makes your answer the most complete here. – BartoszKP Aug 21 '15 at 12:47
2

You are not showing the code which is responsible for the error. But from the error message it looks like you are doing something like this:

sometabwidget->addTab(some_CUDial_instance, ...);

when you should simply be passing a pointer to it:

sometabwidget->addTab(&some_CUDial_instance, ...);
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Bowdzone
  • 3,827
  • 11
  • 39
  • 52
  • 1
    @BartoszKP That does not make this wrong. The answer is not giving any advice about how to store objects, simply that the function needs a pointer instead of a value. The ownership is another topic entirely – Bowdzone Aug 20 '15 at 19:52
  • @BartoszKP Nothing in the answer states anything about the order of construction which would be crucial for leading to a crash or not. But I guess let us just agree to disagree. – Bowdzone Aug 20 '15 at 20:01
  • You are right that it does not have to crash. But it is still against Qt's recommended practice. And may crash in some scenarios. – BartoszKP Aug 20 '15 at 20:06
  • 1
    @BartoszKP "QTabWidget takes ownership of the passed pointer and will attempt to delete it on deconstruction" Only if you don't destruct it first! And that's what you are supposed to do when you hold it by value. "it is still against Qt's recommended practice" I'm sorry, you're making this recommended practice up. Nobody who understands C++ would recommend this. It's completely, 100% bogus. Please stop telling people to prematurely pessimize their code. If you doubt that it works, just look at my examples (see my profile). They all hold objects by value where they can. Doing otherwise is silly – Kuba hasn't forgotten Monica Aug 20 '15 at 20:47
  • @KubaOber I'm not making this up - it's written in plain text in the link I've provided. – BartoszKP Aug 20 '15 at 20:57
  • 1
    @BartoszKP Unless I'm missing something, nothing in the [link](http://doc.qt.io/qt-4.8/objecttrees.html) discourages the practice. On the contrary, the documentation highlights that the order is important and has meaning, and I fully agree. – Kuba hasn't forgotten Monica Aug 20 '15 at 21:17
  • @KubaOber I didn't say the link discourages the practice you propose. I say it encourages the practice that I've described, and it does: in the very first paragraph, and by showing an example of a problem that would not occur if you let Qt manage the life-time of widgets. – BartoszKP Aug 20 '15 at 21:21
  • I don't like the practice but I admit that this answer is not *entirely* wrong. Definitely not that wrong to deserve a downvote, so I'm reversing it. – BartoszKP Aug 20 '15 at 21:23
1

After some discussions in the comments I admit the approach I encourage here is a bit opinion based, so I strongly advise to read all answers under this question. However, please do note the paragraph with statements in bold in this answer.


In general, following Qt's suggestion from the documentation:

QObjects organize themselves in object trees. When you create a QObject with another object as parent, it's added to the parent's children() list, and is deleted when the parent is. It turns out that this approach fits the needs of GUI objects very well. For example, a QShortcut (keyboard shortcut) is a child of the relevant window, so when the user closes that window, the shortcut is deleted too.

you should not hold instances of QWidget types by value, because when adding them to QTabWidget it will become their parent and will attempt to delete them on deconstruction.

The problem lies in the fact that you are passing an object by value (wid1) to a function which requires a pointer (QTabWidget::addTab). Usually you can just change it to &wid1 but it can cause problems in the aforementioned context of Qt's memory management.

In your particular case, it will work fine, because the destruction will start in your class, deleting its members, and they will correctly unregister from QTabWidget, so it won't attempt to delete them again later.

This however can lead to an inflexible code structure - as explained in the linked page of Qt's documentation, where simple reordering of object construction leads to a crash.

Also, you will need to manually reorder the members inside your class, to follow their dependencies as your code evolves. This seems silly, if you don't have to do it: Qt will do just fine managing the life-time of your widgets, and you don't have to worry about it.

Thus, the safest solution is to have a pointer CUDial* wid1, allocate it with new and let QTabWidget manage its life-time.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • "You should not hold instances of QWidget types by value, because when adding them to QTabWidget it will become their parent and will attempt to delete them on deconstruction." This advice is completely bogus. In fact, not holding these instances by value when you can is **premature pessimization** by unnecessary pointer references and loss of locality and every time I see it, I cringe. C++ has well defined order of destruction of members held by value, and you are supposed to leverage that to keep it safe. It works fine! The only problem the OP had was a trivial typo - a missing `&` operator. – Kuba hasn't forgotten Monica Aug 20 '15 at 20:43
  • @KubaOber The documentation clearly presents that when holding by value trivial-looking tweaks of your code may cause a totally unexpected crash. It's not premature "pessimization" to guard against inflexible design. Also, when the documentation clearly states what is the best practice, it's best to hold on to it, especially if your are a beginner. Even though in this scenario by-value membership may work fine, giving such advice to a beginner does not seem a good idea. However, you're right in general, so I'll improve my answer not to be so extreme on the value vs. pointer issue. – BartoszKP Aug 20 '15 at 20:50
  • @KubaOber I've edited the answer: hope it's better now :-) – BartoszKP Aug 20 '15 at 20:55
  • "trivial-looking tweaks of your code may cause a totally unexpected crash" if someone is modifying the order of initialization without understanding its importance, that's doesn't make it a "trivial-looking tweak" in general. It only makes it so to someone who doesn't understand C++. I have thousands of such by-value declarations all over the place, even having widgets in `std::list` and it works wonderfully and produces smaller, easier to manage code that performs slightly better, too. it's a matter of understanding, not blindly following "practice" whose origin is quite different. – Kuba hasn't forgotten Monica Aug 20 '15 at 21:03
  • Rhis "practice" came to be roughly 15-20 years ago when C++ compilers and computers were so slow, that it helped to keep you sane by having as few included headers as possible in your class interfaces. Thus instead of having members declared by value, you declared them by pointer, and forward-declared the classes. The headers for these classes were then included only in the implementation files. Today, this is irrelevant due to better compilers, faster hardware and the [PIMPL idiom](http://stackoverflow.com/q/25250171/1329652) that should shield implementations from your users anyway. – Kuba hasn't forgotten Monica Aug 20 '15 at 21:06
  • @KubaOber As I've said under your answer: implicit dependency is a code smell, and definitely such code is not easier to manage. Imagine you need to reorder your widgets after refactoring related to dependency-inversion principle (happens quite often in large projects). The fact that you must additionally analyze their destruction dependency is a nuisance. Why do you want to do that, when you can leave it to Qt, where it just works everytime? – BartoszKP Aug 20 '15 at 21:08
  • Thanks BartoszKP, in fact that was just a stupid mistake. As you said, I tried to pass an object by value in a function where a pointer was needed. Shame on me! – Patrik Aug 21 '15 at 15:20
0

I found this via search for "qt meta object features not supported for nested classes" (which would have been the next error after the dereference issue, as Kuba's answer explains).

One way to work around the Qt object "nested classes" issue is to forward declare the nested class in the enclosing class. Then declare the nested class outside the encloser. E.g.:

class UDial : public QDialog
{
    Q_OBJECT
    class CUDial;   // forward declare (doesn't have to be private)
    CUDial *wid1;  // would need to be a pointer if you need to hold a reference to it (which maybe you don't)

  public:
    UDial(QWidget *parent = 0);
};

class UDial::CUDial : public QWidget
{
    Q_OBJECT
    CUDial(QWidget *parent = 0);
    void someOtherFunction();
    friend class UDial;  // could also make the constuctor/etc public
};

In source (.cpp), CUDial (and any member functions) would be defined like this:

  UDial::CUDial::CUDial(QWidget *parent) : QWidget(parent) { ... }
  void UDial::CUDial::someOtherFunction() { ... }
Maxim Paperno
  • 4,485
  • 2
  • 18
  • 22