【问题标题】:VirtualTreeView Finalize in C++ Builder for UnicodeStringVirtualTreeView 在 C++ Builder 中完成 UnicodeString
【发布时间】:2012-04-28 07:45:56
【问题描述】:

我在 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),也可能会留下对 UnicodeString 的引用。

如何在 C++ 中为 UnicodeString 释放 OnFreeNode 事件中的节点数据?我知道分配 UnicodeString 直到所有引用计数为零 - 那么如何强制引用计数为零?

另外,如果在OnNodeInit 中分配了节点怎么办 - 在OnFreeNode 事件中同样适用吗?

如果 TVTNodeData 结构是纯虚拟的 - 节点永远不可见,也不使用 AddChild 或 OnNodeInit 初始化,然后需要 Finalize,那么该结构甚至存在于内存中吗?

更新:后来我发现我测量内存使用率不正确,对于字符串,将它们设置为空字符串确实足以清除内存数据。但是 - 正如 Rob Kennedy 在下面的回答中所建议的那样,调用 struct ~destructor 甚至更好,等效于 Finalize 并且更容易,因为它清除了整个结构(如果你有更多的字符串)。

【问题讨论】:

  • 您是否指定了正确的NodeDataSize?还可以尝试验证您添加到树中的每个节点,WideString 也是 Caption 的选项吗?。
  • 是的,我在循环之前做了VTree->NodeDataSize = sizeof(TVTNodeData);。 WideString 是可能的,但使用它有什么好处?它没有引用计数,因此将其设置为空字符串会释放所有内存吗? UnicodeString 不是对 VCL 更友好吗?
  • WideString 是 COM BSTR 类型,由 Windows COM 堆分配器管理,没有引用计数。但简单地将 UnicodeString 设置为空字符串应该等于 Finalize。
  • 谢谢,我发现毕竟没有泄漏。但是我确实发现 WideString 消耗的内存比 UnicodeString 少,你能确认一下吗?
  • 是的,WideString 消耗的内存少于UnicodeStringWidestring 除了字符数据之外只包含一个 4 字节整数(字符串长度),而 UnicodeString 包含两个 4 字节整数(字符串长度和引用计数)和两个 2 字节整数(元素除了字符数据之外,每个字符的大小 - 总是 2 - 和字符串代码页 - 总是 CP_UTF16)。

标签: c++ delphi memory-leaks c++builder virtualtreeview


【解决方案1】:

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

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

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

树控件使用所有位为零来初始化数据。如果您的数据中有一个不是正确初始化的对象(正式正确,包括 all 非 POD 类型),那么您应该调用 constructor OnInitNode 事件中的数据。使用 placement new 来做到这一点。例如:

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

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

如果一个节点从未被初始化,那么它也不会被最终确定。 OnInitNode 事件将永远不会运行,因此树将知道节点尚未初始化。未初始化的节点不会被最终确定,因此您无需担心。

【讨论】:

  • 谢谢,因为我也发现我可以调用 ~TVTNodeData() 但我担心以后可能会产生一些访问冲突(一开始并没有)。我现在也更好地理解了 VirtualTreeView 的机制。感谢您的回答,非常感谢。
【解决方案2】:

我认为并非所有节点都经过验证,可能是因为它们没有显示。尝试在 AddChild 之后调用 ValidateNode。

【讨论】:

  • 所以您是说在 C++ 程序中不需要 Finalize 并且将 UnicodeString 设置为空字符串会清除所有已使用的内存?
  • 经过更多测试后,我发现 ValidateNode 没有任何区别。似乎没有内存泄漏。如果节点未初始化,则不会消耗内存。我确实发现 WideString 消耗的内存比 UnicodeString 少(鉴于列表视图有数千个条目 - 数千个 WideStrings/Unicodestrings),有人可以确认 WideString 消耗更少的内存是真的吗?
猜你喜欢
  • 2020-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多