关于ValueTree深度复制的一则教训与实例

近期在做的项目,牵扯到文件、图像,音频、文本等方面的读写、交换与处理。毋容置疑,JUCE类库的ValueTree类实在是得心应手,方便之极。但是经验有时也不靠谱,轻车熟路,漫不经心的丁点草率,都有可能付出巨大的时间代价。

这两天整合调试某模块时,发现一个奇怪的现象:每次跑起来,不管喂进去多少条“记录”(程序中的概念定为note,一个note包含多项数据),明明总文件每次都保存成功了,MVC三者之间运作协调也良好无误,前台显示正常。但是下次启动,只保留了最后的note,其他都不见了。因总文件用了加密技术,懒得写查看单元,就尝试在已有代码中跟一跟。搞了许久无果,郁闷之至。

就在刚刚,迷迷糊糊中象被人敲了一记闷棍,惊觉ValueTree的常规操作全部基于引用,已经加入上级Tree中的子节点,如果做了removeAllProperties(),那么已添加的,此时铁定已无效。同一个子note,哪怕改头换面往父节点中塞N次,其父始终持有的,只不过是最后一个尚未removeAllProperties()的有效数据而已。

这并非故事的全部。此例中,总节点未采用引用策略,独有的ValueTree直接“代表”了磁盘文件,而总节点数据写入磁盘之后,并未真正更新自身。因为刚刚添加的note随后就removeAllProperties()了,该总节点再次往磁盘上写东西,始终只能写入原有数据和本次操作的最后一条。这就导致了该问题的出现。

解决之道:

  1. 每次写文件之后,更新总节点——显然,这个办法太蛮横。
  2. 子节点添加时做个深度复制——OK,就你了。

翻代码,瞬间找到,连呼三声FxxK……

将源码公布于此,以儆效尤。

const bool WorkArea::noteWasAdded (const ValueTree& note)
{
    /* Here must 'real' update the notebookData, otherwise it would always presented the original file, since the note which passed in will be clear in EditAreea after clicked its OK button, it'll be invalid all of the notes which have been added before (for each time). In this case, only the last note could be saved to the disk-file. So, the argument 'note' must be deep-copied through its createCopy(). */
    notebookData.addChild (note.createCopy(), -1, nullptr);
    
    if (SystemGlobal::writeValueTreeToFile (notebookData, notebookFile))
    {       
        // notice the container to refresh itself
        articlesContainer->noteWasAdded (note);
        viewport->setViewPositionProportionately (0.0, 1.0);

        return true;
    }
    else
    {
        AlertWindow::showMessageBox (AlertWindow::InfoIcon, 
            TRANS ("Information"),
            TRANS ("Something wrong ocurred during this operation."));

        return false;
    }
}