【问题标题】:Is it possible to introduce Automatic Reference Counting (ARC) to C++?是否可以将自动引用计数 (ARC) 引入 C++?
【发布时间】:2012-04-01 19:11:01
【问题描述】:

Objective C 引入了一种称为 ARC 的技术,以将开发人员从内存管理的负担中解放出来。听起来不错,如果g++也有这个功能,我想C++开发者会很高兴的。

ARC 允许您将内存管理的负担交给 (Apple LLVM 3.0) 编译器,而不再考虑保留、释放和自动释放

所以,如果 LLVM3.0 可以做到这一点,我认为 g++ 也可以将 C++ 开发人员从内存管理的繁重工作中解放出来,对吧?

将ARC引入C++有什么困难吗?

我的意思是:如果我们不使用智能指针,我们只使用new/new[],编译器是否可以为我们做一些事情来防止内存泄漏?比如自动将new改为智能指针?

【问题讨论】:

  • 我承认我对 ARC 一无所知,但 C++ 有用于自动内存管理的智能指针(shared_ptr、unique_ptr 等)。你检查过吗?
  • 如果你“做对了”,你应该(几乎)永远不需要在 C++ 中使用new/delete
  • @Mehrdad: new 很好,delete 是坏人 :)
  • @nightcracker: 不,他们同样邪恶。没有deletenew 也是邪恶的:)
  • @Als: 使用智能指针,delete 会自动完成 :)

标签: c++ memory-management


【解决方案1】:

Microsoft C++/CX 具有用于 ref 类的 ARC。 Embarcadero 有 2 个 C++ 编译器,其中一个有 ARC。

【讨论】:

  • 与此同时,Embarcadero 似乎正在逐步淘汰 ARC。
【解决方案2】:

最近我使用 Clang 编写了一些 Objective-C++ 代码,并惊讶地发现 Objective-C 指针实际上在 C++ 中被处理为非 POD 类型,我可以在我的 C++ 类中使用而没有问题。
它们实际上在我的析构函数中被自动释放了!
我用它在 std::vectors 中存储弱引用,因为我想不出一种方法来保存弱引用的 NSArrary..
无论如何,在我看来,Clang 通过在 Objective-C 中模拟 C++ RAII 和智能指针,在 Objective-C 中实现了 ARC。仔细想想,ARC 中的每个 NSObject* 都只是 C++ 中的一个智能指针(来自 Boost 的 intrusive_ptr)。
我可以看到 ARC 和智能指针之间的唯一区别是 ARC 是内置在语言中的。除此之外,它们具有相同的语义。

【讨论】:

    【解决方案3】:

    这是个好问题。 ARC 不仅仅是智能指针的实现。它也不同于垃圾收集,因为它确实让您可以完全控制内存管理。

    在 ARC 中,您确切知道何时释放对象。人们认为不正确的原因是,您没有写明确的“释放”调用。但是你知道编译器什么时候会插入一个。而且它不在某些垃圾收集步骤中,它在认为不再需要对象时是内联的。

    它包含一个编译器步骤,用于分析代码并尝试找到任何冗余的递增和递减引用计数序列。如果给它一个优化器可以看穿的智能指针实现,那么优化的 C++ 编译器可能会实现这一点。

    ARC 还依赖于目标 c 的语义。首先,对指针进行注释以说明它们是强还是弱。这也可以在 C++ 中完成,只需使用两个不同的指针类(或使用智能指针和普通指针)。其次,它依赖于objective c方法的命名约定来知道它们的返回值是隐式弱还是强,这意味着它可以与非ARC代码一起工作(ARC需要知道你的非ARC代码是否打算返回一个对象例如,引用计数为 +1)。如果您的“C ARC”没有与非“C ARC”代码放在一起,您就不需要这个。

    ARC 为您提供的最后一件事是在编译时对您的代码进行非常好的分析,以说明它认为可能存在泄漏的位置。这很难添加到 C++ 代码中,但可以添加到 C++ 编译器中。

    【讨论】:

    • 前两段+1,后两段加1。感谢您的精彩解释!
    【解决方案4】:

    使用 ARC 而不是完整的垃圾回收有什么优势?委员会面前有一个垃圾收集的具体建议;最后,由于时间不够,它没有得到处理,但似乎有大多数委员会(如果不是真正的共识)支持在 C++ 中添加垃圾收集。

    在全球范围内,引用计数不能很好地替代真正的垃圾回收:它在运行时间方面很昂贵,并且需要特殊的代码来处理循环。但是,它适用于特定的有限情况,C++ 应程序员的要求通过std::shared_ptr 提供它,当他知道它适用时。

    【讨论】:

    • 我的理解是 Apple 的员工对 GC 性能并不感到兴奋(因此 iOS 上没有它),但他们意识到如果他们足够了解在运行时执行垃圾收集,他们可以在编译时实现类似的逻辑并消除对 GC 的需求。 ARC 就是这种转变的结果。
    • @Clay 我不知道;我从未在苹果公司工作过。但是根据程序在做什么,GC 可以胜过手动内存管理;在其他情况下,它会显着提高性能,因为运行时成本可以安排在程序不做任何其他事情时发生。
    • @JamesKanze 一种罕见的情况是软实时交互式动画。如果你想要恒定的 60fps,你需要控制每帧的 CPU 负载。简单的 GC 实现将负载推迟到特定时间点,这使得 GC 无法用于此类应用程序。这在无法在多台机器上扩展的用户设备上尤其重要。与普通工程师预期的不同,对 60fps 的需求要微不足道。
    • 增量 GC 可以通过尝试随时间分配负载而不是延迟负载来改善这一点,但您仍然无法控制,并且负载是不可预测的。所以最终可能会出现尖峰。 (大多数情况下)Real-time GC 理论上可以解决这个问题,但没有什么是众所周知和证明的。而且,仍然无法预测。而且我认为实时 GC 不会比 RC 表现更好。
    • @Eonil 实际上,我认为交互式动画是垃圾收集大放异彩的一个案例。在最佳条件下(即当您拥有最少数量的活动指针时),您在完成每一帧后触发收集器;否则,它所花费的总时间将少于释放内存所需的时间。 (当然,根据实际的分配模式,可能有优于垃圾回收和经典 malloc/free 的解决方案。)
    【解决方案5】:
    1. 已经有一些针对 C++ 的类似技术的实现;例如,Boehm-Demers-Weiser garbage collector
    2. C++11 有一个special Application Binary Interface 供任何希望添加自己的垃圾收集的人使用。
    3. 在绝大多数情况下,smart pointers 等技术可以为 C++ 开发人员轻松完成内存管理工作。

    【讨论】:

    • Re 3:我想说在绝大多数情况下,没有任何真正的内存管理问题需要解决。 C++ 具有值语义,这意味着动态分配用于具有外部指定生命周期的实体对象(在这种情况下,何时调用 delete 由要求指定,并且您不能使用任何类型的引用计数),或实现动态大小的结构(如std::vector),在这种情况下,数据结构有自己的析构函数,可以负责释放内存。
    • @JamesKanze 是也不是。并非所有智能指针都具有引用计数的概念,并且大多数智能指针都有根据请求释放资源的方法;例如shared_ptr::reset。我会使用一个智能指针,比如scoper_ptr,即使在一个对象中,正如你所说,“外部指定的生命周期”,如果我被要求释放内存,只需调用reset
    • 当“初始化”一个实体对象(在更大的意义上)时,可能需要将它保存在auto_ptr 中,直到初始化完成,并且该对象能够承担它的完整职责(通常,为各种类型的通知和事件注册自己)。仅当无法在构造函数中完全初始化对象时才需要这样做,但是这种情况并不常见。 (在很多情况下,我只会做new,忽略它返回的指针。)
    【解决方案6】:

    看看Qt。 Qt 通过利用层次链实现了这个特性。您可以新建一个指针并为其分配父级,Qt 将帮助您管理内存。

    【讨论】:

      【解决方案7】:

      使用 C++ 的原因之一是完全控制内存管理。如果您不希望在特定情况下这样做,可以使用智能指针为您进行管理。

      存在托管内存解决方案,但在正确选择 C++ 的情况下(对于大型大型应用程序),它不是一个可行的选择。

      【讨论】:

      • @Cat Plus Plus:也许,但在代码的绝对性能最关键部分(例如:标准容器)中可能不需要托管指针的开销(多么最小)。但在 99% 的情况下,你当然是对的。
      • unique_ptr 没有开销,这应该是指针的默认选择。
      • @Cat Plus Plus:那么请忽略我所说的,有时我很愚蠢(我是 C++ 菜鸟 :))。
      • @CatPlusPlus:与函数调用(构造函数和析构函数)相关的开销如何?
      • @JamesKanze: boost::scoped_ptr 然后。 unique_ptr 代表专有所有权,而不是临时所有权,无论如何。它与原始指针的区别仅在于安全级别(您可以通过get 提取非拥有指针并将其传递到您喜欢的任何地方)。你永远不应该拥有一个原始指针,这是我认识的每个 C++ 专家的声明。
      【解决方案8】:

      没必要。我们已经共享了为我们执行此操作的指针。事实上,我们为各种不同的情况提供了一系列指针类型,但共享指针完全模仿了 ARC 所做的事情。

      见:

      std::shared_ptr<>

      boost::shared_ptr<>

      【讨论】:

      • 我想知道他们是否这样做?我对ARC一无所知,所以我不能肯定。但是任何系统地使用引用计数的东西都必须对循环进行某种特殊处理。否则,弊大于利。 (毕竟,使用std::shared_ptr&lt;&gt; 的情况很少。)
      • @JamesKanze,由于 OP 中提到的引用计数语义,我更喜欢 shared_ptr。 unique_ptr 不需要这个,尽管所有这些智能指针类确实意味着我们可以不用担心 delete
      • 让我困惑的是ARC是编译器的一个特性,但智能指针与编译器没有关系,对吧?
      • @Camino,正确的。 C++ 编译器没有智能指针、引用计数等概念。这些是 STL、Boost 等库提供的功能。
      • @Moo-Juice,您知道为什么 LLVM3.0 可以帮助改善内存管理吗?我的意思是 ARC 声明它是一个编译器功能,但我不知道编译器如何解决内存管理问题。
      【解决方案9】:

      C++ 有 Resource Allocation is Initialization(RAII) 的概念,智能地使用这种方法可以让你免于显式的资源管理。

      C++ 已经提供了shared_ptr,它提供了引用计数。

      此外,还有许多其他 Smart pointers 使用 RAII 让您在 C++ 中的生活更轻松。

      【讨论】:

      • 另外:与动态分配相比,您应该更喜欢值和自动变量,并在真正需要时保留后者。
      • @Camino:Cat 所说的是一条有价值的建议,请务必遵守。在这方面,C++-Faq Entry 对您来说是一本好书。
      • 但是 OP 的问题是关于 Automatic RC。 ARC 的重点是编译器在它们应该属于的地方引入了正确的生命周期/所有权指令。也就是说,源代码除了new 的等价物外没有任何管理指令——因此问题是,如果 LLVM 可以为 Objective-C 做到这一点,它作为 C++ 中的编译器选项在技术上是否可行?如果不是,那么 C++ 和 OC 的区别是什么?
      • shared_ptr 但是始终使用互斥体/临界区,即使您没有将指针传递给另一个线程。
      • @entonio ARC 和 shared_ptr 之间的功能区别是什么?在这两种情况下,编译器都会添加引用计数指令...
      猜你喜欢
      • 1970-01-01
      • 2012-01-11
      • 2012-03-21
      • 2012-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-20
      • 1970-01-01
      相关资源
      最近更新 更多