【问题标题】:How would you replace the 'new' keyword?您将如何替换“新”关键字?
【发布时间】:2009-08-13 07:03:35
【问题描述】:

很久以前我找到了一篇文章(我在 ATM 上找不到),其中说明了 C++ 中的 new 关键字不好的原因。我不记得所有的原因,但我记得最清楚的两个是你必须将 new 与 delete 匹配,new[] 与 delete[] 匹配,并且你不能像 malloc 那样将#define 与 new 一起使用。

我正在设计一种语言,所以我想问一下您将如何更改 C++ 语言,使新语言更加友好。随意陈述新文章和文章的问题。我希望我能找到文章链接,但我记得它很长,是由 (IIRC) 一所知名学校的教授写的。

【问题讨论】:

  • malloc() 显然也很糟糕,因为您必须将其与 free() 匹配
  • 我不会让#define-ability 影响任何关于语法的决定——这就是龙...
  • 如果您无法就这种语言设计决策的利弊表达自己的看法,是什么让您认为您已准备好改进它?
  • 这是您想的文章吗? c2.com/cgi/wiki?NewConsideredHarmful
  • 或者这可能是有问题的文章:scs.stanford.edu/~dm/home/papers/c++-new.html

标签: c++ language-design new-operator keyword


【解决方案1】:

我看不出有任何理由用其他东西替换 new 关键字(而且似乎 C++ 委员会同意我的观点)。它很清楚,并做出了它应该做的事情。你可以在你的类中覆盖operator new,不需要使用定义。

要消除new[]/delete[] 问题,您可以使用std::vector

如果你想使用智能指针,你可以使用它,但我想控制何时使用智能指针。这就是为什么我喜欢它在 C++ 中的工作方式——能够控制低级细节的高级行为。

【讨论】:

  • 同意。相反,我会替换 Java 中的 new 关键字,因为无论如何您都不能将对象放在堆栈上!
  • 请注意,boost/tr1/std::array 不是/不会替代new[]/delete[]。 (那将是std::vector。) 至于你的论点:一方面,我可以看到让 RTE 照顾 deletedelete[] 的优势。 OTOH,C++ 提供了很多替代方案(如std::vector)来完全避免动态分配。但是,正如我在stackoverflow.com/questions/1245430/… 中所说,这主要是关于如何教授 C++ 的问题,而不是它提供了什么。
  • tr1::array 是 new[]/delete[] 的替代品。它可以在堆栈上分配数组。 std::vector 总是在堆上分配内存。
  • 不,Jla3ep,array 不能替代 new[]。如果你使用new[],那是因为你直到运行时才知道元素的数量。但是array 有一个非类型模板参数指示它的大小,并且这样的参数必须是编译时常量。如果您在编译时知道所需的元素数量,那么您一开始就不应该进行动态分配。 array 是固定大小数组的替代品,而不是用new[] 分配的数组。为此,请使用vector
  • 哦,是的......从那一边 :) 修正了我的答案。
【解决方案2】:

问题匹配new、delete、new[]、delete[]

没什么大不了的。
您应该将内存分配包装在一个类中,因此这不会真正影响普通用户。单个对象可以用智能指针包装。而数组可以用 std::Vector

表示

不能像使用 malloc 一样使用 #define 和 new。

像这样搞乱 malloc 的原因是在您的应用程序和标准内存管理层之间引入您自己的内存管理层。这是因为在 C 语言中不允许您编写自己的 malloc 版本。在 C++ 中,编写自己的 new 版本是非常合法的,这使得这个技巧变得不必要。

【讨论】:

    【解决方案3】:

    我会给它 C# 中 new 的语义(或多或少):

    1. 为对象分配内存。
    2. 通过将成员变量设置为其默认值来初始化内存(通常为 0 表示值,null 表示引用)。
    3. 初始化对象的动态绑定机制(C++ 中的 vtables,托管 VM 的类型 def 表)。
    4. 调用构造函数,此时虚拟调用按预期工作。
    5. 对于没有垃圾回收的语言(此时是新语言的 eww),从调用中返回 smart_ptr 或类似名称。

    此外,将所有对象设为值类型或引用类型,这样您就不必保留显式的smart_ptr。只允许new 为引用类型进行堆分配,并确保它包含正确调用析构函数的信息。对于值类型,new 从堆栈中调用内存上的构造函数。

    【讨论】:

    • 我不知道,我喜欢 D 这样做的方式。垃圾收集,但有能力做 RAII。
    • try/finallyusing 之间,我从不觉得自己缺少任何东西。
    • 假定所有值类型都是“小”(即适合堆栈)。我认为巨大的矩阵是值类型,但肯定不属于堆栈。
    • @Drew:不是关于详细信息的线程,但我实际上正在研究矩阵和 GC/JIT 之间的一些有趣的(至少对我而言)合作,以在托管中满足 C/C++ 速度用于科学计算的虚拟机。如果我没有被束缚在这里上学,那可能就是我想找工作的地方。 :D
    • 我,我缺少组合子对象的using(即成员字段)。
    【解决方案4】:

    使用垃圾收集,这样您就无需将新的东西与任何东西相匹配。

    【讨论】:

    • 同意。 C++ 使用一种非常通用的垃圾回收机制,称为智能指针。
    • 听,听! RAII 比非确定性 GC 更强大!
    • 但是智能指针比现代垃圾收集器慢得多。
    • 而智能指针不做同样的工作——释放周期——并且通常需要为每次获取或释放进行锁定(至少是总线级别),而不是 gc 所需的粗略锁定。
    • 我有一个证明——这不适合这里的空白——表明 GC 不一定比 RAII 更有效,因为对于每个 GC 实现,在此之上都有一个微不足道的 RAII 实现.
    【解决方案5】:

    通过使用 STL 容器类和各种 boost:smart_ptrs,几乎不需要在 C++ 代码中显式调用 new 或 delete。

    您可能需要调用 new 的几个地方(例如,初始化智能指针)使用命名构造器惯用语来返回包裹在其中的类类型指针,例如,boost:shared_ptr

    但是 C++ 和 STL 非常努力地允许您将大多数对象视为值对象,因此您可以构造对象而不是指针并直接使用它们。

    鉴于这一切,几乎不需要替换 new 运算符 - 这样做会带来许多问题,无论是需要垃圾收集器,还是减少 C++ 为程序员提供的精细低级控制。

    【讨论】:

      【解决方案6】:

      如果您的 new 语言被垃圾回收,您可以避免使用 new 关键字。这就是 Python 所做的(而 Lisp 几乎在 5 年前就做到了!)。另请参阅 Peter Norvig 提供的类似问题的答案here。 (没有“新闻”是好消息吗?)

      【讨论】:

      • 在 common lisp 中,'new' 的等价物是 'make-instance',所以它是一个函数而不是关键字/特殊形式,但在其他方面类似(采用对象的类及其初始化参数)。
      • make-instance 是 CLOS 的一部分,而后者又在 Lisp 本身中实现。 (SICP 和“元对象协议的艺术”将对此进行更多说明)。请注意,make-instance 不会进行任何 VM 级别的调用来创建对象的新实例。事实上,Lisp VM 并不知道类和对象的存在。所以这不能与Java等遵循的实现策略相提并论。
      • SCIP 以方案中的对象系统作为教学辅助工具。专业的 Common Lisp 实现(例如 Allegro)实现了“本机”CLOS,而不是使用 conses 来获得巨大的性能提升。
      • 同意。 BTW,CLOS是如何实现的?它是在 Lisp 本身中实现的。最后,对象变成了有状态的过程。这在 SICP 中进行了简单说明,并在“元对象协议的艺术”中进行了详细说明。关键是,make-instance 不应与 new 混淆。
      【解决方案7】:

      有时您想用工厂替换构造函数。这是一个众所周知的重构。 Replace Constructor With Factory Method。所以也许这就是这篇文章的意思?

      顺便说一句,您经常会看到直接调用 new 被替换为 Factory Method

      Unity 等 DI 框架将这一概念提升到了另一个层次。正如您在以下 C# 代码中看到的,没有应用“new”来创建 IMyClass 接口:

      IUnityContainer myContainer = new UnityContainer();
      myContainer.RegisterType<IMyClass, SomeClass>();
      IMyClass thing = myContainer.Resolve<IMyClass>();
      

      【讨论】:

      • 在我的一个实验性 C++ 项目中,我实际上用 XML 编写了我的类声明并为它们生成了头文件,其中构造函数受到保护但暴露了一个静态 Create 方法,该方法为它们提供了良好的语义并返回智能指针。 :)
      【解决方案8】:

      C++ 有一个单独的 new 运算符(或 C malloc )的原因主要是为了创建生命周期超过创建它们的函数范围的对象。

      如果你有尾调用消除和延续,你不会在意 - 对象都可以在堆栈上创建并且具有无限范围 - 一个对象可以存在,直到你调用与超出范围的对象对应的延续并被破坏。然后,您可能需要一些东西来进行垃圾收集或以其他方式压缩堆栈,以免它充满不再需要的对象( Chicken Scheme 和 TinyOS 2 是两个不同的示例,用于在运行时或编译时间;Chicken Scheme 不允许 RAII,TinyOS 不允许真正的动态分配),尽管对于大量代码,这样的方案与 RAII 并没有太大的不同,因为它可以选择更改顺序对象被破坏。

      【讨论】:

        猜你喜欢
        • 2014-12-22
        • 2021-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-19
        • 2022-01-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多