2

I'm using QCustomPlot and have sub-classed QCPGraph in order to provide a drawable graph.

class QCPDrawableGraph : public QCPGraph {
    Q_OBJECT
public:
    QCPDrawableGraph(QCPAxis* x, QCPAxis* y) : QCPGraph(x,y) {
        //do stuff
    }
    virtual ~QCPDrawabelGraph() {} // necessary?
    //class stuff
};

Usually, one would create new graphs by

QCustomPlot plot(parent); //where parent is the parent widget of the gui
QCPGraph* gr = plot->addGraph(); // in case of QCPGraphs or
QCPGraph* gr = new QCPGraph(plot->xAxis,plot->yAxis); // as with all other QCPAbstractPlottables

Would I use my own class just like

QCPDrawableGraph* dgr = new QCPDrawableGraph(plot->xAxis,plot->yAxis); //?

Does the destructor of QCustomPlot still take care of the de-allocation in the end?

LCsa
  • 607
  • 9
  • 30
  • Is your question whether you need to deallocate if you subclass a class that does deallocate automatically? – jackw11111 Aug 31 '18 at 01:19
  • 1
    I...think so, yes. Whether I need to delete something here, manually. I know from looking a bit through the source code that the destructor of `QCustomPlot` destroys all `QCPAbstractDrawable`s it owns of which `QCPGraph` and thus also my class are inherited. I guess the destructor of the parent `QCPGraph` automatically invokes the one of the child `QCPDrawableGraph`? I haven't fully wrapped my mind about that. Right now my bells just ring and it screams "Where is the delete to cover your new?????" – LCsa Aug 31 '18 at 07:00
  • 1
    This is correct. Each destructor will invoke its parent destructor up the inheritance heirarchy. `virtual` destructors are only important when a pointer to a base class points to a subclassed instance, for example, `QCPGraph* gr = new QCPDrawableGraph(plot->xAxis,plot->yAxis);`, in which case, it would be important for the base class destructor to be `virtual`, otherwise the `QCPGraph`s destructor would be called instead of `QCPDrawableGraph`s destructor. – jackw11111 Aug 31 '18 at 07:14
  • @jackw11111 So destructors are only called "upwards", meaning child objects calling parent destructors? So in my case, only the "`QCPGraph`-part" of `QCPDrawableGraph` is handled by the `QCustomPlot` which only calls `QCPGraph`'s destructor, no? I'm slow on the uptake right now. Creating a `QCPGraph` automatically puts it in the responsibility of a `QCustomPlot` object. Does this hold true for a child of `QCPGraph` as well as long as the parent object inside it is initialized correctly? Furthermore, wouldn't it be safe to always make destructors `virtual` in derived classes? Thanks. – LCsa Aug 31 '18 at 11:35
  • Yes, that all seems correct. When you create a QDrawableGraph instance, QCustomPlot invokes its constructor, and its child, QCPGraph, constructor, which in turn invokes its child, QDrawableGraph, constructor. Deallocating is just the reverse. The QDrawableGraph instance invokes its destructor and then its parent, QCGraph, destructor, QCGraph then invoke its parent, QCustomPlot, destructor. I'm not sure what effect making derived class destructors virtual has but definitely that its safer to make base class destructors virtual. – jackw11111 Aug 31 '18 at 20:35

1 Answers1

1

The general concept of QWidgets memory management is that parent widgets care about deletion of children if they are deleted itself.

A QWidget becomes child of another if either the parent is given in constructor (nearly every widget constructor offers a parent pointer) or the child is added to parent widget.

This is the case for OP's QCPDrawableGraph as well.

It is explicitly mentioned in the doc. of QPCGraph (Constructor & Destructor Documentation):

The created QCPGraph is automatically registered with the QCustomPlot instance inferred from keyAxis. This QCustomPlot instance takes ownership of the QCPGraph, so do not delete it manually but use QCustomPlot::removePlottable() instead.

As the constructor of OP's QCPDrawableGraph

QCPDrawableGraph(QCPAxis* x, QCPAxis* y) : QCPGraph(x,y) {
    //do stuff
}

calls the base constructor this behavior should be inherited properly.


Concerning the destruction a little sample:

#include <iostream>

struct Base {
  virtual ~Base() { std::cout << "Base::~Base()\n"; }
};

struct Derived: Base {
  ~Derived() { std::cout << "Derived::~Derived()\n"; }
};

int main()
{
  Base *p = new Derived();
  delete p;
  return 0;
}

Output:

Derived::~Derived()
Base::~Base()

Live Demo on ideone

Notes:

  1. The destructor ~Derived() is virtual even without the virtual keyword because the destructor of its base class Base is.

  2. The destructor ~Derived() is called first though by deleting a pointer to base class Base. (That's the intention of virtual destructors.)

  3. The destructors of all base classes are called as well (as well as constructors but in reverse order).

LCsa
  • 607
  • 9
  • 30
Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • When reading the doc I still wasn't sure about whether it holds true for my child class. Thank you for clarifying! Would you declare the child class destructor as virtual in my case? Only in the case of an allocation `QCPGraph* gr = new QCPDrawableGraph(...)`? – LCsa Aug 31 '18 at 11:21
  • @LCsa Yes, it must be `virtual`. However, if the base class destructor is `virtual` (and I bet it is) then the child class destructor becomes `virtual` as well, even without the `virtual` keyword, and even if not defined. (In the latter case, it's the default destructor.) – Scheff's Cat Aug 31 '18 at 11:32
  • @LCsa Concerning your doubts about destruction, I extended the answer a bit. (This has nothing to do with Qt - it's like C++ does it in general.) – Scheff's Cat Aug 31 '18 at 11:43
  • @note 1: "even without", no? Can't edit, because it's to few characters. :) Thank you for the explanation!! Am I correct that a class that will be inherited by a child needs a `virtual` destructor? What happens if it doesn't have one? Playing with your code shows me: The child destructor isn't called and I have created a memory leak... – LCsa Aug 31 '18 at 11:53
  • 1
    @LCsa That's the point I forgot to mention. The destructor of base class must be `virtual`. Otherwise, it's not working. But again - I bet it is. – Scheff's Cat Aug 31 '18 at 11:55
  • So in case the parent destructor is NOT `virtual`, the child destructor is NOT called when doing `Base* p = new Derived(); delete p;`. However, `Derived* p = new Derived(); delete p;` would still work fine, wouldn't it? Why would I NOT make the destructor of my class `virtual`? If it isn't, one can not sub-class my class without risking the chance for memory leaks... – LCsa Aug 31 '18 at 11:59
  • @LCsa Actually, it's sufficient that any of the base classes has a virtual destructor. If a class has a virtual destructor any directly or indirectly derived has a well whether explicitly defined or left out (and generated by compiler). – Scheff's Cat Aug 31 '18 at 12:03
  • @LCsa If in doubt, just write a `qDebug()` into your destructor like I did in the demo sample. – Scheff's Cat Aug 31 '18 at 12:04
  • Thank you! Your explanations were very enlightening! I'm just asking myself why I would ever write a class with a non `virtual` destructor... – LCsa Aug 31 '18 at 12:08
  • 1
    @LCsa There are good reasons to do so - if you want to have small data bundles which are not dedicated for inheritance. Having a virtual destructor may be the (only) reason why a `class`/`struct` is stuffed with a VMT (Virtual Method Table) and, hence, not plain-old data anymore. E.g. the std containers are usually not intended for inheritance - and doesn't have virtual destructors. – Scheff's Cat Aug 31 '18 at 12:11