在C++ Builder中为UnicodeString完成VirtualTreeView

4

我在C++ Builder中使用VirtualTreeView,并使用以下结构:

struct TVTNodeData
   {
   int Index;
   UnicodeString Caption;
   }

我使用循环来预填充树的节点,循环代码如下:

TVirtualNode *Node = VTree->AddChild(NULL);
pNode = (TVTNodeData *)VTree->GetNodeData(Node);
pNode->Index = 1;
pNode->Caption = "Whatever";

我注意到应用程序的内存一直在增加(内存泄漏),即使我清除树形结构并重新加载。这个页面 - http://www.remkoweijnen.nl/blog/2010/06/09/memory-leaks-when-using-virtual-treeview-component/ 建议在 OnFreeNode 事件中执行 Finalize()。目前为止还不错。

但是 C++ 中没有 Finalize()。我尝试在 OnFreeNode 事件中使用 pNode->Caption="",虽然分配的内存减少了,但仍然有点。我认为可能还有对 UnicodeString 的引用,尽管它已经被清空了(引用计数 > 0)。

在 C++ 中,如何在 OnFreeNode 事件中释放 UnicodeString 的节点数据?我知道 UnicodeString 在所有引用计数为零之前都会被分配 - 那么我如何强制引用计数变为零呢?

另外,在 OnNodeInit 中分配节点时,情况是否相同适用于 OnFreeNode 事件呢?

如果TVTNodeData结构是纯虚拟的——节点既不可见,也不会通过AddChild或OnNodeInit进行初始化,那么是否需要Finalize?在这种情况下,该结构是否存在于内存中?
更新:后来我发现我错误地测量了内存使用情况,并且对于字符串来说,将它们设置为空字符串就足以清除内存数据。但正如Rob Kennedy在他下面的回答中建议的那样,调用struct ~destructor甚至更好,相当于Finalize,而且更容易,因为它清除整个结构(如果您在其中有更多的字符串)。

你是否指定了正确的 NodeDataSize?同时尝试验证您添加到树中的每个节点,此外,WideString 是否是标题的选项? - kobik
是的,在循环之前我执行了“VTree->NodeDataSize = sizeof(TVTNodeData);”。使用WideString是可能的,但使用它有什么优势呢?它没有引用计数,所以将其设置为空字符串会释放所有内存?UnicodeString不是更适合VCL吗? - Coder12345
3
WideString是COM BSTR类型,由Windows COM堆分配器进行管理,没有引用计数。但是将UnicodeString设置为空字符串应该等同于Finalize。 - kobik
谢谢,我发现最终没有内存泄漏。但是我发现 WideString 消耗的内存比 UnicodeString 少,你能确认一下吗? - Coder12345
2
是的,WideString 消耗的内存比 UnicodeString 少。Widestring 仅包含一个4字节整数(字符串长度)以及字符数据,而 UnicodeString 包含两个4字节整数(字符串长度和引用计数)和两个2字节整数 (每个字符的元素大小 - 始终为2 - 和字符串代码页 - 始终为 CP_UTF16)以及字符数据。 - Remy Lebeau
2个回答

8

Delphi中的Finalize的作用是释放记录中任何由编译器管理的类型。在C++中,这通常是类型的析构函数的工作。在您的OnFreeNode事件处理程序中,直接调用数据类型的析构函数:

TVTNodeData* const pNode = static_cast<TVTNodeData*>(Sender->GetNodeData(Node));
pNode->~TVTNodeData();

这将调用UnicodeString对象的析构函数,从而释放相关联的字符数据。当树形控件为节点分配TVTNodeData时,它位于与TVirtualNode对象本身相同的内存块中,因此您不能仅调用delete

树形控件使用全零比特初始化数据。如果您的数据中有一个对象,其不是正确的初始化(严格来说,包括所有非POD类型),则应在OnInitNode事件中调用数据的构造函数。使用placement new来完成。例如:

TVTNodeData* const pNode = static_cast<TVTNodeData*>(Sender->GetNodeData(Node));
new (pNode) TVTNodeData();

这将调用TVTNodeData成员的构造函数,而不会为额外的TVTNodeData实例分配内存。

如果一个节点从未被初始化,那么它也不会被销毁。因为OnInitNode事件从未运行过,所以树知道该节点尚未被初始化。未初始化的节点不会被销毁,所以您无需担心。


谢谢,我也发现我可以调用~TVTNodeData(),但我担心这可能会在以后产生一些访问冲突(起初并没有)。我现在更好地理解了VirtualTreeView的机制。感谢您的回答,非常感激。 - Coder12345

2
我认为你的节点并没有全部验证,可能是因为它们没有被显示出来。尝试在添加子节点后调用ValidateNode。

1
那么你的意思是在C++程序中不需要使用Finalize,将UnicodeString设置为空字符串可以清除其使用的所有内存? - Coder12345
经过更多的测试,我发现ValidateNode没有任何区别。似乎没有内存泄漏。如果节点未初始化,则不会消耗任何内存。但是我确实发现WideString比UnicodeString消耗更少的内存(考虑到列表视图有数千个条目-数千个WideStrings / Unicodestrings),有人可以确认WideString是否真的消耗更少的内存吗? - Coder12345

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接