C ++中的new运算符与java中的new运算符之间的区别

据我所知, new运营商做了以下事情:(如果我错了,请纠正我。)

  1. 分配内存,然后返回分配的内存的第一个块的引用。 (很明显,内存是从堆中分配的。)
  2. 初始化对象(调用构造函数。)

运算符new[]以类似的方式工作,除了它对数组中的每个元素执行此操作。

任何人都可以告诉我这两个运算符在C ++和Java中有何不同:

  1. 就他们的生命周期而言。
  2. 如果他们无法分配内存怎么办?

  • 在C ++中, T * p = new T;

    1. T类型的对象分配足够的内存,

    2. 在该内存中构造一个T类型的对象,可能正在初始化它,以及

    3. 返回指向该对象的指针。 (指针与标准new的已分配内存的地址具有相同的值,但不一定是new[]数组的情况。)

    如果内存分配失败,则抛出类型为std::bad_alloc的exception,不构造任何对象且不分配内存。

    如果对象构造函数抛出exception,(显然)没有构造对象,则会立即自动释放内存,并传播exception。

    否则,构造了一个动态分配的对象,用户必须手动销毁该对象并释放内存,通常是说delete p;

    实际的分配和释放function可以用C ++控制。 如果没有别的,则使用全局的预定义函数::operator new() ,但这可以由用户替换 ; 如果存在静态成员函数T::operator new ,则将使用该函数。

  • 在Java中它非常相似,只是new的返回值可以绑定到类型为T的Java变量(或其基类,如Object ),并且必须始终有一个初始化器(所以你要说T x = new T(); )。 对象的生命周期是不确定的,但保证至少与任何变量仍引用对象一样长,并且无法(也不需要)手动销毁对象。 Java没有明确的内存概念,您无法控制分配的内部。

此外,C ++允许许多不同forms的new表达式(所谓的放置forms)。 它们都创建动态存储对象,必须手动销毁,但它们可能相当随意。 据我所知,Java没有这样的设施。


最大的区别可能就是使用 :在Java中,你总是使用new所有事情,而且你必须这样做,因为它是创建(类类型)对象的唯一方法。 相比之下,在C ++中,你几乎不应该在用户代码中拥有裸露的new 。 C ++具有无约束变量,因此变量本身可以是对象,这就是C ++中通常使用对象的方式。

在你的“语句”中,我不认为“返回对分配的内存的第一个块的引用是非常正确的new返回一个指针(对于分配的对象的类型)。这与引用略有不同,虽然在概念上类似。

您的问题的答案:

  1. 在C ++中,一个对象留在内存中(参见注释),直到用deletedelete []显式删除它(并且你必须使用与你分配的匹配的那个,所以一个new int[1];尽管它是相同的作为new int;的内存量new int;不能用delete (反之亦然, delete []不能用于new int )。在Java中,内存在将来的某个时刻被垃圾回收器释放一次有“没有提到记忆”。
  2. 两者都抛出exception(C ++抛出std::bad_alloc ,Java就像OutOfMemoryError),但在C ++中你可以使用new(std::nothrow) ... ,在这种情况下,如果没有足够的可用内存, new将返回NULL满足电话。

注意:根据评论,技术上可以“破坏”对象而不释放它的内存。 这是一个相当不寻常的案例,除非你真的有C ++经验并且你有充分的理由这样做,否则你应该做的事情。 典型的用例是在delete操作符内部,对应于一个placement new(其中new用一个已经存在的内存地址调用,只执行对象的构造)。 同样,placement new非常特别地使用new,而不是你可以期望在普通C ++代码中看到的东西。

您似乎具有new正确操作,因为它分配和初始化内存。

一旦new成功完成,程序员就会负责delete该内存。 确保发生这种情况的最佳方法是永远不要直接使用new ,而是选择标准容器和算法,以及基于堆栈的对象。 但是如果你确实需要分配内存,那么C ++习惯用法就是使用一个智能指针,如C ++ 11中的unique_ptr或者来自boost或C ++ 11的shared_ptr 。 这确保了内存被正确回收。

如果分配失败,则在清除失败之前构造的对象的任何部分之后, new调用将抛出exception。 您可以使用new的(nothrow)版本返回空指针而不是抛出exception,但这会给客户端代码带来更多的清理负担。

我不知道Java中的细节,但这里是C ++中newnew[]

  1. 分配内存

    当你有一个表达式new Tnew T(args) ,编译器会确定要调用哪个函数来获取内存

    • 如果类型T具有适当的成员operator new ,则调用该operator new
    • 否则,如果用户提供了适当的全局operator new ,则调用该operator new

      如果operator new无法分配所请求的内存,则它会调用一个新的处理函数,您可以使用set_new_handler进行设置。 该函数可以释放一些空间,因此分配可以成功,它可以终止程序,或者它可能抛出std::bad_alloc类型的exception或从中派生。 默认的新处理程序只抛出std::bad_alloc

      对于new T[n] operator new[]也会发生同样的情况,除了调用operator new[]进行内存分配。

  2. 构造对象resp。 新分配的内存中的对象。

    对于new T(args) ,调用对象的相应构造函数。 如果构造函数抛出exception,则通过调用相应的operator delete (可以在与operator new相同的位置找到)来释放内存。

    对于new T它取决于T是否为POD(即内置类型或基本上是C结构/联合)。 如果T是POD,则没有任何反应,否则它将被视为new T()

    对于new T[n] ,它还取决于T是否为POD 。 同样,POD未初始化。 对于非POD,依次为每个对象调用默认构造函数。 如果一个对象的默认构造函数抛出,则不会调用其他构造函数,但是已经构造的对象(不包括构造函数刚刚抛出的对象)将以相反的顺序被破坏(即,调用析构函数)。 然后使用适当的operator delete[]释放内存。

  3. 返回指向新创建的对象的指针。 请注意,对于new[] ,指针可能不会指向已分配内存的开头,因为可能会有一些关于构造对象之前已分配对象数的信息, delete[]来确定有多少对象毁灭

在所有情况下,对象一直存在,直到它们被delete ptr (对于使用普通new分配的对象)或delete[] ptr (对于使用array new T[n]创建的对象)进行销毁。 除非添加了第三方库,否则C ++中没有垃圾收集。

请注意,您还可以直接调用operator newoperator delete来分配原始内存。 对于operator new[]operator delete[]也是如此。 但请注意,即使对于那些低级函数,您也可以不混合调用,例如通过使用operator new[]分配的operator delete释放内存。

您还可以使用所谓的placement new来分配已分配内存中的对象(无论您如何获得)。 这是通过将指向原始内存的指针作为new参数来完成的,如下所示: new(pMem) T(args) 。 要破坏这样一个显式构造的对象,你可以直接调用对象的析构函数, p->~T()

通过调用operator newoperator new作为附加参数并将其返回来放置new工作。 这种相同的机制也可用于向operator new提供其他信息,这些operator new重载采用相应的附加参数。 但是,虽然您可以定义相应的operator delete ,但这些操作仅用于在对象在构造期间抛出exception时进行清理。 没有“放置删除”语法。

C ++已经提供的另一种使用放置新语法的方法并不新鲜。 那个采用额外的参数std::nothrow并且与普通的new不同,只是在分配失败时返回空指针。

另请注意, new不是C ++中唯一的内存管理机制。 一方面,有C函数mallocfree 。 通常operator newoperator new[]只调用malloc ,但这并不能保证。 因此,您可能不会混合使用这些表单(例如,通过在指向使用operator new分配的内存的指针上调用free )。 另一方面,STL容器通过分配器处理它们的分配,分配器是管理对象的分配/释放以及容器中对象的构造/销毁的对象。

最后,有些对象的生命周期由语言直接控制,即静态和自动生命周期。 通过简单地在本地范围定义类型的变量来分配自动生命周期对象。 它们在执行通过该行时自动创建,并在执行离开作用域时自动销毁(包括通过exception保留作用域)。 静态生存期对象使用关键字static在全局/命名空间范围或本地范围定义。 它们是在程序启动时创建的(全局/命名空间范围),或者当它们的定义行被最终执行时(本地范围),它们一直存在到程序结束时,它们以相反的构造顺序自动销毁。

通常,自动或静态变量优先于动态分配(即,您使用new或allocators分配的所有内容),因为编译器需要适当的销毁,不像动态分配,您必须自己完成。 如果您有动态分配的对象,则由于同样的原因,最好使用自动/静态对象(容器,智能指针)来管理它们的生命周期。