I’m returning to C++ after decades away. I’m familiar with the RAII principles, but evidently not with the implementation.
What am I doing wrong?
class Bindings : public juce::Component, public juce::TableListBoxModel
{
public:
void setTableValueTree(juce::ValueTree TableValueTree)
{
// I've narrowed it down to this routine.
data = TableValueTree.createXml();
dataList.reset(data->getChildByName("DATA"));
columnList.reset(data->getChildByName("HEADERS"));
numRows = dataList->getNumChildElements();
data.release();
setColumns();
}
private:
/* Do various TableListBox things here. */
std::unique_ptr<juce::XmlElement> data;
std::unique_ptr<juce::XmlElement> columnList;
std::unique_ptr<juce::XmlElement> dataList;
};
Running this triggers an exception on program exit:
numObjects = {value=1 }
getLeakedObjectClassName = 0x00007ff7cbc47c00 {Mobius.exe!juce::LeakedObjectDetectorjuce::XmlElement::getLeakedObjectClassName(void)}
I appreciate the help, but that still doesn’t help me understand where I went wrong in my original code. Let’s say I moved “data.release()” to the destructor. The call to setTableValueTree still causes a leaked object.
Calling “release” on a unique_ptr tells the unique_ptr to just forget about the pointer and not delete the managed object. That’s the cause of your leak.
Instead, don’t do anything! The unique_ptr will automatically delete the managed object when Bindings is destroyed.
If you prefer, do this in your destructor:
data = nullptr;
That will explicitly delete the managed object. That can be useful if you need to control the order in which your members are freed.
In any case, I think @benvining’s solution is better; createXml returns a unique_ptr which is kept as a local variable. When the local variable goes out of scope, the managed object is deleted.
class Bindings : public juce::Component, public juce::TableListBoxModel
{
public:
void setTableValueTree(juce::ValueTree TableValueTree)
{
auto data = TableValueTree.createXml();
dataList.reset (data->getChildByName("DATA"));
columnList.reset (data->getChildByName("HEADERS"));
numRows = dataList->getNumChildElements();
setColumns();
}
private:
/* Do various TableListBox things here. */
std::unique_ptr<juce::XmlElement> columnList;
std::unique_ptr<juce::XmlElement> dataList;
};
All that has been said is correct, but I believe there’s another subtle problem in this code:
{
auto data = TableValueTree.createXml();
dataList.reset (data->getChildByName("DATA"));
columnList.reset (data->getChildByName("HEADERS"));
numRows = dataList->getNumChildElements();
setColumns();
}
the std::unique_ptr <XmlElement> returned by createXml() owns the whole hierarchy of XML nodes that descend from it.
The two calls to getChildByName() return non-owning raw pointers (XmlElement*) to nodes within that hierarchy, and as soon as data goes out of scope, the whole hierarchy of XML nodes is deleted and both dataList and columnList become dangling pointers.
Good shout, I was so focused on the leak that I missed that.
If you want to keep information from the tree I strongly recommend ValueTree. Here the ownership is managed by reference counted sharedObjects wrapped in ValueTrees that you can copy at no cost and pass around easily.
And both boils down to XML internally and can easily be serialized from and to XML.