C++/CX能检测和解决对象循环吗?

12

据我的理解,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 如何确定这些对象中哪一个应该成为“根对象”,哪一个应该成为“弱引用”?


为什么它需要检测循环呢?既然两者都在使用中,当您删除这两个对象中的最后一个时,它既不能释放其中任何一个,也无法处理此情况。完成。 - RedX
已经有一些著名的循环检测算法用于引用计数垃圾收集器... https://secure.wikimedia.org/wikipedia/en/wiki/Reference_counting#Dealing_with_reference_cycles - CAFxX
@RedX:如果C++/WinRT不能处理循环引用,应用程序将会泄漏内存。 - dalle
2
@CAFxX:WinRT 不使用垃圾回收机制。 - dalle
2
WinRT本身支持弱引用 - 请查看“C:\Program Files (x86)\Windows Kits\8.0\Include\winrt\WeakReference.idl”。但我还不知道它如何投射到VC++语言扩展中。 - Pavel Minaev
6个回答

12

简短回答:不,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赋值,有一个隐式转换为未指定布尔类型等等。

注意,在VS11 Beta中,如果您尝试使用WeakReference,IDE Intellisense会出现警告,用波浪线等方式进行标记。尽管如此,编译器仍然可以很好地处理它们。


哎呀,这又是一个不创建C++/CX扩展并坚持使用我们都想要的weak_ptr<wrapper class>的原因啊 :) - gbjbaanb
@gbjbaanb 请记住,这描述了开发者预览版的现状。这并不意味着未来版本将保持不变。 - Pavel Minaev
当然,如果微软听取我们的意见并将那个讨厌的.NET风格界面改成更本地化的界面,我会为其鼓掌。我想这是微软为模块规范做出贡献的机会,不幸的是它没有被纳入最新标准,现在有了一个Boost::Modules,也可以作为访问WinRT组件的一种方式,那就太好了。 - gbjbaanb
@gbjbaanb C++/CX 并非强制使用,如果您喜欢加入智能指针模板等的纯 C++,则可以使用它 - 所有 WinRT 接口都提供常规 IDL 文件,并且 WRL 是带有 ComPtr<T> 等其他内容的辅助库。请参阅此视频以获取更多详细信息:http://channel9.msdn.com/Shows/C9-GoingNative/GoingNative-2-C-at-BUILD-Windows-Runtime-LibraryWRL-Meet-Tarek-and-Sridhar - Pavel Minaev
我已经重写了答案,以匹配beta版中的当前情况(其中C++/CX具有适当的弱引用支持)。 - Pavel Minaev

1

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


我尝试使用reinterpret_cast和相关技术创建一个示例,将T^转换为IWeakReferenceSource*,但是对于通过ref class定义的类,QI返回E_NOINTERFACE - 看起来这些类不支持弱接口(还没有)?但是对于来自Windows.*的WinRT组件有效。 - Pavel Minaev

0

Mozilla XPCOM实现了Bacon的方法,如果需要的话,这个方法在一定程度上可以移植到WinRT。通常情况下,避免追踪垃圾收集器是一件好事。开发人员仍然有很多泄漏内存的方式,所以最好不要陷入幻想。此外,从控制的角度来看,循环所有权是没有意义的。这就像Munchausen用自己的头发把自己从泥潭中拉出来并让自己漂浮在空中一样荒谬。每个对象存在的原因必须有一个理由,引用计数是这个理由的体现。另一个体现是修改的权利,这导致了高度依赖于引用计数可用性的强大的写时复制技术。在只有追踪垃圾收集的环境中,人们要么对可变数据结构进行深拷贝以保护它免受不必要的变异,要么使用带有小型深层更改大量惩罚的不可变数据结构。或者来回转换可变和不可变的数据结构。此外,据估计,当有5倍所需RAM可用时,追踪垃圾收集才能正常工作(深度复制的副本不计算在内)。相比之下,保守分配器使用2倍所需RAM(由于碎片而浪费)。不要被骗了,拷贝垃圾收集器只会使分配更快,但浪费的RAM比引用计数保守垃圾收集器多2.5倍,以实现可比较的性能。

看看苹果。他们将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中,抢占其他应用程序的页面。这就像永动机一样不断地进行着。正常的永动机可以从空气中无限制地产生能量,而反之则会浪费无穷无尽的能量。跟踪垃圾回收是一种永远不会结束的算法。它是根据启发式方法定期启动的,只有在完成后才知道是否成功。也许这一次我们会幸运地收集到一些东西,也许第二次、第三次。你把电脑留给它很长时间,希望它最终完成它的工作,让你继续工作,但这项工作永远不会结束。突然间,两个应用程序同时运行跟踪垃圾回收,并开始竞争未交换的RAM。跟踪垃圾回收很可能对同一页进行多次连续访问,因此一个页面可以多次进出交换区。在办公环境中,只有老板的电脑可能有大量的RAM,其他电脑尽可能便宜。此外,杀毒软件被强制部署到每台办公电脑上,办公员工无法摆脱它。杀毒软件为内存签名保留RAM,使其更加稀缺,并检查包括交换文件在内的每个I/O,导致冻结到完全疯狂。这就是地球上的地狱。

我问了一些追踪垃圾收集器的支持者,是否像我一样遇到了卡顿问题。结果发现他们在自己的电脑上装了很多内存(比如笔记本上有16GB!!!),并且使用单用户模式,这样垃圾回收就能正常工作。但是如果他们不得不在最便宜的办公电脑上强制安装杀毒软件来开发,那将会是地狱。

所以我建议你不要只看循环收集问题。学会喜欢引用计数,享受它,并让用户享受你的精简程序。充分利用引用计数。为嵌套结构提供强大的写时复制功能。使用包装连接的数据库连接池,在连接包装不再被引用时立即将连接返回到池中。网络透明性。RAII。

如果你真的没有其他选择,可以借鉴Mozilla XPCOM。顺便说一下,在Windows操作系统上,据说Mozilla XPCOM的ABI与Microsoft COM相同,但不确定。


0

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

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

一个可能的解决方法是使用“本地”C++实现WinRT类而不使用C++/CX扩展。 WRL框架大大简化了这项任务(它为WinRT做了ATL为COM所做的工作)。

其中一个WinRT示例(“DLL服务器作者”示例)展示了如何在不使用ref的情况下实现WinRT对象。 默认情况下,从WRL::RuntimeClass<Interface>继承的类自动实现IWeakReferenceSource,因此提供弱引用。


0

这仍然是传统的COM编程方式,需要手动思考并添加显式的减少引用计数调用。


有时候这不是一个选项,比如在OnLaunched中设置Window::Current->Content = foo并返回之前。 - dalle
@Pavel:Decref看起来可能像t = nullptr; - Ben Voigt
IWeakReference,所以这个答案并不完全正确。不过需要了解如何在C++/CX中使用弱引用。 - Pavel Minaev
从迄今为止听到的内容来看,这个回答是最准确的。虽然有很多空话,但没有具体的东西。 预防性+1。 - Hans Passant

-5

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

Win32对象一直都是引用计数的,因此似乎WinRT在这方面没有改变任何内容。它只是为您提供了C++ RAII类,在Win32程序员编写自己的包装器以利用RAII之前。


4
这不是C++/CLI,而是VC++组件扩展。语法(大部分)相同,但语义不同。在VC++/CX中,“ref class”可以计数引用,并且复制“T^”将自动更新它,无需使用垃圾回收机制。 - Pavel Minaev
@Pavel:等等,同样的代码可以是正确的C++/CLI,也可以是错误的VC++组件扩展?这个想法需要立即消失。或者至少要求在每个文件的顶部使用#pragma指定编写它的构建环境。 - Ben Voigt
哦,现在我理解了,你可以根据ref newgcnew来判断它是为哪个环境所用。但在我看来,这仍然是一个非常容易出问题的想法。 - Ben Voigt
那里还有其他的区别(例如,cli现在是lang,而System::StringPlatform::String),但你说得对,一个人可以编写与C++/CLI在视觉上无法区分但具有不同语义的代码。不过,C++/CLI在Metro样式应用程序中没有官方支持。文档在这里:http://msdn.microsoft.com/en-us/library/windows/apps/hh454076(v=VS.85).aspx;此外,也可以看看Herb的讲座:http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-532T——从9:20开始。 - Pavel Minaev

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