C++/Java实现中的new[]
看到一篇中文文章《C/C++, Java: Java的new[]与C++的new[]》很有意思。
public class Test extends JPanel {
private static final long serialVersionUID = 4767050156491994899L;
public static void main(String[] args) {
AnimApp[] array = new AnimApp[3]; // 没有执行构造函数, 在这里只是申请了空间.
array[0] = new AnimApp(3); // 构造一个对象
}
}
class AnimApp {
public AnimApp(int times) {
System.out.println("class AnimApp");
}
}
文章提到:
Java中的定义一个数组, 然后用new[], 这时并没有执行构造函数, 只是申请了一段内存空间, 与C++中的allocator<T>.allocate(size)(因为allocator<T>的类型在运行时确定, 所以不是指定空间的字节数, 而是用元素个数, 每个元素的大小allocator中有记录)相似. 然后在构造一个对象的时候, 即如上new AnimApp(3)时, 才真正的构造对象, 似allocator<T>.construct(param).
但在C++中, new[]操作符(默认的)就会去先申请空间, 接着执行构造函数, 申请了多少个对象的空间, 执行多少次(每个对象一次), 所以想为无无参构造函数的类使用默认的new[]来定义一个数组是不行的, 这点与Java不同, 他是即申请空间, 同时也要构造对象, Java只是申请一段空间, 对于空间中的每个对象, 得自己显示的用new ClassName(param)来构造.
这两步让我想到了Symbian下提到的"两段构造"技术。即一个对象的生成分为两个阶段:1)为对象分配空间.2)调用对象的构造函数为分配的空间初始化。
在这两个过程中,都可能发生失败,特别是构造函数发生异常的时候,第一阶段分配的内存空间就会失去句柄,导致内存泄露(Memory Leak)。由于Symbian下的C++在C++标准化之前(至少是没有标准化的异常处理的),所以Symbian提出了自己的处理模式——”两段构造“。
参考《Symbian OS异常三步曲之三:两段构造》如下
一、两段构造的格式:
编写一个类时,将构造代码分为两部分:
1、 一个基本的不会发生异常退出的构造函数
这个构造函数将被new操作符调用,它隐式的调用基类的构造函数,另外还可以调用那些不会发生异常退出的函数,也可以以默认值或传入构造函数的参数来初始化成员变量。
2、 一个类方法(通常称为ConstructL())
只要通过new操作符分配并构造的对象被压入了清除栈,该方法就可以单独调用了,它将继续完成对象的构造过程,这里面可以安全的执行那些可能发生异常退出的操作。即使发生了异常,由于之前已经将对象指针入栈,清除栈可以调用类的析构函数来释放所有已经成功分配的资源,并回收分配给对象本身的内存。
相关的参考代码如下
class CExample : public CBase
{
public:
static CExample* NewL();//静态函数
static CExample* NewLC();//静态函数
~CExample();//析构函数必须public,否则delete不能调用
private:
CExample();//绝对不能异常
ConstructL();//将构造时所有可能异常的代码放在这里
}
CExample* CExample::NewLC()
{
CExample* me = new(ELeave) CExample();
CleanupStack::PushL(me);
me->ConstrutL();
return me;
}
CExample* CExample::NewL()
{
CExample me = CExample::NewLC();
CleanupStack::Pop(me);
return me;
}
当然,NewL()和NewLC()函数是可以接受参数的,并可以通过参数来初始化对象。这些参数可以传递给简单构造函数,也可以传递给ContructL(),或者同时传递给两者。
————————————————————我是分割线——————————————
对于Java这种与C++不同之处的原因要归为它们之间的实现不同。
1)Java的数组不是集合,它只能保存同种类型的多个原始类型或者对象的引用。数组保存的仅仅是对象的引用,而不是对象本身。
2)数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。
3)在Java中,官方推荐的声明方式是Object []array,并且不能指定长度,只有new的时候才指定长度。
4)对象类型数组中的引用被默认初始化为null。如:Object [] myobj= new Object [10]; 相当于从myobj[0]到myobj[9]都这样被自动初始化为myCar[i] = null;并没有调用构造函数。在myobj[1] = new Object的时候才调用。
至于Java为什么要选择这种方式,则是其刻意避免指针(容易犯错),符号都是引用类型。