C++/CX 是不是检测和解决对象的循环?

Posted

技术标签:

【中文标题】C++/CX 是不是检测和解决对象的循环?【英文标题】:Does C++/CX detect and solve cycles of objects?C++/CX 是否检测和解决对象的循环? 【发布时间】:2011-11-18 11:10:20 【问题描述】:

来自我的understanding C++/CX 不使用垃圾收集,而是使用引用计数方法。

引用计数的问题在于它不能处理循环。循环通常使用弱引用解决,例如标准 C++ 中的weak_ptr。

但我在 C++/CX 中找不到明确指定弱引用的方法。据此,我假设这是由 C++/CX 本身处理的。我想知道 C++/CX 如何解决这个问题。

例如,看下面的代码:

ref class Foo

public:
    Bar^ bar;
;

ref class Bar

public:
    Foo^ foo;
;

ref class App

public:
    virtual void OnLaunched(LaunchActivatedEventArgs^ args)
    
        Foo^ foo = ref new Foo();
        Bar^ bar = ref new Bar();
        foo.bar = bar;
        bar.foo = foo;
    
;

C++/CX 如何检测这个循环?

C++/CX 如何解决这个循环?

C++/CX 如何决定这些对象中哪一个是“根对象”,哪一个是“弱引用”?

【问题讨论】:

为什么要检测周期?它不能同时释放,因为两者都在使用中,当您删除这两个对象中的最后一个时,两者都将被释放。完成。 有众所周知的循环检测算法用于引用计数 GC...secure.wikimedia.org/wikipedia/en/wiki/… @RedX:如果 C++/WinRT 不处理周期,应用程序将泄漏内存。 @CAFxX:WinRT 不使用 GC。 WinRT 本身支持弱引用 - 查看“C:\Program Files (x86)\Windows Kits\8.0\Include\winrt\WeakReference.idl”。不过,我还不知道它是如何投射到 VC++ 语言扩展上的。 【参考方案1】:

简答:不,C++/CX 不检测和解决对象的循环。

长答案:WinRT 本身具有弱引用的标准机制。在 ABI 级别,这是根据接口 IWeakReferenceIWeakReferenceSource 定义的,您可以在“%ProgramFiles%\Windows Kits\8.0\Include\winrt\WeakReference.idl”中看到它们。

在 C++/CX 中,所有类都会自动实现 IWeakReferenceSource,因此它们的所有实例都可以被弱引用。要获取和存储对对象的弱引用,您应该使用帮助器类Platform::WeakReference(在 vccorlib.h 中定义):

Foo^ foo = ref new Foo;
Platform::WeakReference weakRef(foo);

要取回对象,请使用Resolve<T>:

foo = weakRef.Resolve<Foo>();

像往常一样,你会得到nullptr是对象已经被销毁了。

除此之外,WeakReference 的实例的行为或多或少类似于智能指针 - 它是可复制的、可移动的、可比较的、可从 nullptr 分配、隐式转换为未指定的 bool 类型等。

请注意,从 VS11 Beta 开始,如果您尝试使用 IDE Intellisense,它将拒绝WeakReference,并用曲线等下划线。尽管如此,编译器仍然可以很好地处理它们。

【讨论】:

嗯。另一个不创建 C++/CX 扩展并坚持使用weak_ptr 的另一个原因,就像我们都希望的那样:) @gbjbaanb 请记住,这描述了开发人员预览版的现状。这并不意味着在未来的版本中一切都会保持不变。 当然,如果 Microsoft 能够倾听我们的意见并将讨厌的 .NET 样式界面更改为更具原生外观的界面,那么我会为它喝彩。我想这是 MS 为模块规范做出贡献的机会,不幸的是,它没有成为最新的标准,现在是 Boost::Modules,它也可以作为访问 WinRT 组件的一种方式,这很好。 @gbjbaanb C++/CX 不是强加给你的——如果你更喜欢带有智能指针模板等的 vanilla C++,你可以使用它——为所有 WinRT 接口提供常规 IDL 文件,WRL 是帮助程序带有ComPtr&lt;T&gt; 和其他东西的图​​书馆。有关详细信息,请参阅此视频:channel9.msdn.com/Shows/C9-GoingNative/… 我已经重写了答案以匹配 beta 中的当前状态(在 C++/CX 中具有适当的弱引用支持)。【参考方案2】:

查看 SDK 中的 Include\winrt\WeakReference.h。它定义了可用于此目的的 IWeakReference。

【讨论】:

我尝试使用reinterpret_cast 和朋友从T^IWeakReferenceSource* 制作样本,但是QI 为通过ref class 定义的类重新调整E_NOINTERFACE - 看起来那些不t 支持弱接口(还)?不过,它适用于来自Windows.* 的 WinRT 组件。【参考方案3】:

它将与旧的 COM 编程方式、手动思考和添加显式 decref 调用相同。

【讨论】:

有时这不是一个选项,例如在返回之前在OnLaunched 中设置Window::Current-&gt;Content = foo 时。 @Pavel:Decref 可能看起来像 t = nullptr; IWeakReference 所以这个答案并不完全正确。不过需要了解如何在 C++/CX 中进行弱引用。 从我目前听到的情况来看,这个答案是最准确的。有很多人在挥手,但没有什么具体的。抢先 +1。【参考方案4】:

正如 Pavel Minaev 所说,WinRT 具有弱引用的标准机制:IWeakReferenceSource/IWeakReference 接口、WRL::WeakRef 辅助类等等。

不幸的是,通过ref class 定义的类没有实现IWeakReferenceSource,至少在这个开发者预览版中,我找不到任何方法来添加这个接口。

一种可能的解决方法是在“本机”C++ 中实现 WinRT 类而不使用 C++/CX 扩展。 WRL 框架大大简化了这项任务(它对 WinRT 的作用就像 ATL 对 COM 所做的那样)。

有一个 WinRT 示例(“DLL 服务器创作”示例)展示了如何在不使用 ref 的情况下实现 WinRT 对象。 默认情况下,继承自 WRL::RuntimeClass&lt;Interface&gt; 的类会自动实现 IWeakReferenceSource,因此会提供弱引用。

【讨论】:

【参考方案5】:

Mozilla XPCOM implemented Bacon's approach,如果需要,这个在某种程度上可以移植到 WinRT。一般来说,避免跟踪垃圾收集器是一件好事。开发者还有plenty of ways to leak memory,所以最好不要误入歧途。此外,从控制的角度来看,循环所有权没有意义。这就像蒙乔森抓住自己的头发把自己从泥潭中拉出来,让自己漂浮在空中。每一个对象的存在都必须有一个理由,而引用计数就是这个理由的体现。另一种表现形式是修改权,它产生了高度依赖于引用计数器可用性的强大的写时复制技术。在仅具有跟踪垃圾收集的环境中,要么必须执行可变数据结构的深层复制以保护其免受不需要的突变,要么使用不可变数据结构,对小的深度变化有很大的惩罚。或者来回转换可变和不可变数据结构。此外,估计跟踪垃圾收集only works fine when there is 5x required RAM available(深度复制的副本未计入)。相比之下,保守的分配器使用 2 倍所需的 RAM(由于碎片而浪费)。别被骗了,复制垃圾收集器只会使分配更快,但为了达到可比的性能,比引用计数的保守垃圾收集器浪费 2.5 倍的 RAM。

看看苹果。他们将 TGC 作为可选功能引入到 Objective-C 2.0 中,但随后弃用了它,大量的 iPhone 应用程序在没有它的情况下也能正常工作。 iPhone 以出色的用户体验和较长的电池充电时间而闻名。 Windows 10 在我的 4Gb RAM PC 上死机,而 Mac OS X 10.4.10 Hackintosh 在 1Gb RAM 上运行得非常顺利。也许这在某种程度上是相关的,你不这么认为吗?也许内存在某处意外泄漏,但与冻结和巨大的 RAM 消耗相比,最终很难观察到。

大量的 RAM 消耗使程序交换到磁盘,如果它们交换到磁盘然后开始跟踪垃圾收集,交换的页面将全部返回到 RAM,并且将交换的页面移动回 RAM 非常慢。此外,这样做时,必须抢占其他应用程序的页面以交换文件。众所周知,跟踪垃圾收集的应用程序使用 2.5 倍的 RAM,因此这些应用程序有 2.5 倍的机会进行交换。突然,另一个应用程序也将启动垃圾收集,并且必须将交换的页面返回 RAM,抢占另一个应用程序的页面。它像永续移动一样去来去去,反之亦然。普通永动机无限地凭空产生能量,永动机反之亦然无限浪费能量。跟踪垃圾收集是一种永无止境的算法。它不时以启发式方式启动,并且只有在运气好时才知道何时完成。也许这一次我们会很幸运地收集到一些东西,也许是第二次,也许是第三次。你离开PC很长一段时间,希望它最终会完成它的业务并最终让你工作,但这个业务永远不会结束。突然,两个应用程序同时运行跟踪垃圾收集并开始竞争未交换的 RAM。跟踪垃圾回收很可能会对同一个页面进行多次后续访问,因此一个页面可以多次往返交换。在办公环境中,只有老板的 PC 可能有很多 RAM,其他 PC 则尽可能便宜。此外,杀毒软件被强行部署到每台办公电脑上,办公人员无法摆脱它。防病毒软件会为内存中的签名保留 RAM,使其更加稀缺,并且还会检查每个 I/O,包括交换文件驱动冻结以完全疯狂。这就是地球上的地狱所在。

我询问了追踪垃圾收集器的倡导者是否可以像我一样观察到冻结,结果发现他们将大量 RAM 放入 PC 中(就像笔记本电脑上的 16Gb !!!),在单用户模式下使用它,垃圾收集以这种方式对他们来说很好。见鬼,他们将不得不在最便宜的办公 PC 上进行开发,并强制部署了防病毒软件。

所以我建议你不要只看循环收集问题。学会爱上引用计数,享受它,让用户喜欢你的苗条程序。充分利用引用计数。嵌套结构的强大的写时复制。带有包装连接的数据库连接池,当不再引用它们的包装器时,连接会立即返回到池中。网络透明。 RAII。

如果您真的没有其他选择,请向 Mozilla XPCOM 借用。顺便说一句,在 Windows 操作系统上,Mozilla XPCOM 被告知具有与 Microsoft COM 相同的 ABI,但不确定。

【讨论】:

【参考方案6】:

这些不是 WinRT 对象,它们是您的自定义类型的对象。因为您已使用 C++/CLI 语法将它们声明为 .NET 引用类型 (ref class),所以它们像所有 .NET 引用类型一样通过可达性测试而不是引用计数进行垃圾收集。

Win32 对象一直是被引用计数的,所以 WinRT 似乎并没有改变那里的任何东西。它只是为您提供了 C++ RAII 类,在 Win32 下,程序员编写了自己的包装器来利用 RAII。

【讨论】:

这不是 C++/CLI,这是 VC++ 组件扩展。语法(大部分)相同,语义不同。在 VC++/CX 中,ref class 可引用的,复制T^ 会自动更新它。不涉及 GC。 @Pavel:等等,所以相同的代码可以是正确的 C++/CLI 和损坏的 VC++ 组件扩展?这种想法需要立即消亡。或者至少要求在每个文件的顶部有一个#pragma,以指定它是为哪个构建环境编写的。 哦,现在我明白了,您可以根据ref newgcnew 判断它适用于哪个环境。 IMO,这仍然是一个非常容易出问题的想法。 那里还有其他差异(例如,cli 现在是 langSystem::StringPlatform::String),但你是对的,可以编写在视觉上与 C++ 没有区别的代码/CLI 但具有不同的语义。但是,Metro 风格的应用程序并未正式支持 C++/CLI。文档在这里:msdn.microsoft.com/en-us/library/windows/apps/…;也可以在这里查看 Herb 的演讲:channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-532T - 9:20 开始

以上是关于C++/CX 是不是检测和解决对象的循环?的主要内容,如果未能解决你的问题,请参考以下文章

Spring-bean的循环依赖以及解决方式

多个循环计数器公用cx的解决办法

Blazor:尝试保存项目时检测到对象循环

EF中Json序列化对象时检测到循环引用的解决办法

C#+ArcEngine中com对象的释放问题

Spring 如何解决循环依赖的问题