C ++与Java构造函数
根据John C. Mitchell的说法 – 编程语言中的概念,
[…] Java保证在创建对象时调用构造函数。 […]
这被指向Java的特性,使其在行为上与C ++不同。 所以我必须争辩说,在某些情况下,即使创建了该类的对象,C ++也不会为类调用任何构造函数。
我认为这种情况发生在inheritance发生时,但我无法弄清楚这种情况的一个例子。
你知道任何一个例子吗?
如果您的类定义了至少一个构造函数,那么该语言将不允许您在不调用构造函数的情况下构造该类型的对象。
如果您的类没有定义构造函数,那么一般规则是将调用编译器生成的默认构造函数。
正如其他海报所提到的, 如果您的类是POD类型,则有些情况下您的对象将保持未初始化状态。 但这不是因为编译器“没有调用构造函数”。 这是因为类型没有构造函数(或者它没有什么构造函数),并且有些特殊处理。 但话说回来,Java中不存在POD类型,因此无法真正进行比较。
你也可以破解事物,以便不调用构造函数。 例如,分配char
的缓冲区,获取指向第一个char的指针并将其强制转换为对象类型。 当然,大多数情况下都是未定义的行为,因此它并非真正 “允许”,但编译器通常不会抱怨。
但最重要的是,任何提出这类声明但没有明确说明他们所指的特定角落情况的书籍, 很可能充满了垃圾。 然后,大多数写C ++的人实际上并不太了解这种语言,所以这不应该是一个惊喜。
在Java中有两种情况(我不知道更多),其中可以构造一个类’而不调用它的构造函数,而不会导致在C或类似的情况下入侵:
- 在反序列化期间,可序列化类没有调用它们的构造函数。 序列化机制调用最派生的非可序列化类的no-arg构造函数(在Sun实现中,通过不可validation的字节码)。
- 当使用邪恶的
Object.clone
。
因此,构造函数始终在Java中调用的主张是错误的。
对于声明构造函数的C ++类型,不能在不使用构造函数的情况下创建这些类型的实例。 例如:
class A { public: A( int x ) : n( x ) {} private: int n; };
不使用A(int)构造函数创建A的instancev是不可能的,除非通过复制,在这个实例中将使用合成的复制构造函数。 在任何一种情况下,都必须使用构造函数。
Java构造函数可以调用同一个类的另一个构造函数。 在C ++中是不可能的。 http://www.parashift.com/c++-faq-lite/ctors.html
POD(普通旧数据类型)不是通过C ++中的构造函数初始化的:
struct SimpleClass { int m_nNumber; double m_fAnother; }; SimpleClass simpleobj = { 0 }; SimpleClass simpleobj2 = { 1, 0.5 };
在这两种情况下都没有调用构造函数,甚至都没有生成默认构造函数:
- 使用no initializer声明的非const POD对象具有“不确定的初始值”。
- POD对象的默认初始化为零初始化。 ( http://www.fnal.gov/docs/working-groups/fpcltf/Pkg/ISOcxx/doc/POD.html )
但是,如果SimpleClass本身定义了一个构造函数,SimpleClass将不再是POD,并且总是会调用其中一个构造函数。
在C ++中,当实例化对象时,必须调用该类的构造函数。
在C ++中有一些特殊情况,其中不会调用构造函数。 特别是对于POD类型,在某些情况下不会调用隐式定义的默认构造函数 。
struct X { int x; }; int main() { X x; // implicit default constructor not called // No guarantee in the value of xx X x1 = X(); // creates a temporary, calls its default constructor // and copies that into x1. x1.x is guaranteed to be 0 }
我不太记得可能发生的所有情况,但我似乎记得它主要是在这种情况下。
为了进一步解决这个问题:
这被指向Java的特性,使其在行为上与C ++不同。 所以我必须争辩说,在某些情况下,即使创建了该类的对象,C ++也不会为类调用任何构造函数。
是的,使用POD类型,您可以实例化对象,不会调用任何构造函数。 原因是
这当然是为了与C的兼容性而完成的。
(正如尼尔所说)
我认为这种情况发生在inheritance发生时,但我无法弄清楚这种情况的一个例子。
这与inheritance无关,但与实例化的对象类型无关。
Java实际上可以在不调用任何构造函数的情况下分配对象。
如果浏览ObjectInputStream
的源代码,您会发现它在不调用任何构造函数的情况下分配反序列化的对象。
允许您这样做的方法不是公共API的一部分,而是在sun.*
包中。 但是,请不要告诉我它不是语言的一部分。 您可以使用公共API执行的操作是将反序列化对象的字节流放在一起,读取它并在那里使用从未调用过构造函数的对象实例!
给出一个解释,我有一个建议,为什么作者说对于Java,没有寻找任何我认为不能解决问题的极端情况:你可以想到例如POD不是对象。
C ++具有不安全类型转换的事实更为人所知。 例如,使用C和C ++的简单混合,您可以这样做:
class A { int x; public: A() : X(0) {} virtual void f() { x=x+1; } virtual int getX() { return x; } }; int main() { A *a = (A *)malloc(sizeof(A)); cout << a->getX(); free(a); }
这是一个完全可以接受的C ++程序,它使用未经检查的类型转换forms来避免构造函数调用。 在这种情况下,x未初始化,因此我们可能期望出现不可预测的行为。
但是,可能还有其他情况下Java也无法应用此规则,即使您确定该对象已经以某种方式构建(除非您执行某些操作,否则提及序列化对象是完全合理和正确的)当然是序列化编码)。
只有当你重载新的操作符函数时,才会调用构造函数(它用于避免构造函数调用),否则它的标准是在创建对象时调用构造函数。
void * operator new ( size_t size ) { void *p = malloc(size); if(p) return p; else cout<(operator new(sizeof(X)*5)); // no ctor called }
据我所知,Meyers在他的“Effective C ++”中说,只有在控制流程达到构造函数结束时才会创建对象。 否则它不是一个对象。 每当你想为实际对象虐待一些原始内存时,你可以这样做:
class SomeClass { int Foo, int Bar; }; SomeClass* createButNotConstruct() { char * ptrMem = new char[ sizeof(SomeClass) ]; return reinterpret_cast(ptrMem); }
你不会在这里遇到任何构造函数,但你可能会想,你正在操作一个新创建的对象(并且有很好的时间来调试它);
试图清楚地了解C ++。 答案中有很多不精确的陈述。
在C ++中,POD和类的行为方式相同。 始终调用构造函数。 对于POD,默认构造函数不执行任何操作:它不会初始化值。 但是说没有调用构造函数是错误的。
即使inheritance,也会调用构造函数。
class A { public: A() {} }; class B: public A { public: B() {} // Even if not explicitely stated, class A constructor WILL be called! };
这似乎归结为定义术语“对象”,因此该陈述是重言式。 具体来说,就Java而言,他显然将“对象”定义为一个类的实例。 关于C ++,他(显然)使用更广泛的对象定义,包括甚至没有构造函数的原始类型。
然而,无论他的定义如何,C ++和Java在这方面都比不同。 两者都有原始类型,甚至没有构造函数。 两者都支持创建用户定义的类型,以保证在创建对象时调用构造函数。
C ++还支持用户定义类型的创建(在非常特定的限制内),这些类型不一定在所有可能的情况下调用构造函数。 但是,对此有严格的限制。 其中之一是构造函数必须是“微不足道的” – 即它必须是一个构造函数,它不会执行由编译器自动合成的任何内容。
换句话说,如果您使用构造函数编写类,则编译器保证在适当的时间使用它(例如,如果您编写复制构造函数,则将使用您的复制构造函数创建所有副本)。 如果编写默认构造函数,编译器将使用它来生成不提供初始化程序的所有类型的对象,依此类推。
即使在我们使用静态分配的内存缓冲区来创建对象的情况下,也会调用构造函数。
可以在下面的代码片段中看到。 我还没有看到任何一般情况下没有调用构造函数,但是有很多东西要看:)
包括
使用命名空间std;
class Object
{
上市:
宾语();
〜对象();
};
内联Object :: Object()
{
cout <<“构造函数\ n”;
};
内联对象::〜对象()
{
cout <<“析构函数\ n”;
};
int main()
{
char buffer [2 * sizeof(Object)];
Object * obj = new(buffer)Object; //贴装新的第一个对象
new(buffer + sizeof(Object))对象; //贴装新的第二个对象
//删除obj; //不要这样做
。OBJ [0]〜对象(); //销毁第一个对象
。OBJ [1]〜对象(); //销毁第二个对象
}
在Java中,有些情况下没有调用构造函数。
例如,当反序列化类时,将调用类型层次结构中第一个非可序列化类的默认构造函数,但不调用当前类的构造函数。 Object.clone
避免调用构造函数。 您也可以生成字节码并自己完成。
要理解这是如何可能的,即使JRE中没有本机代码魔法,也只需查看Java字节码即可。 当在Java代码中使用new
关键字时,会从中生成两个字节码指令 – 首先使用new
指令分配实例,然后使用invokespecial
指令调用构造函数。
如果您生成自己的字节码(例如使用ASM),则可以更改invokespecial
指令以调用实际类型的超类的构造函数之一(例如java.lang.Object
)的构造函数,甚至可以完全跳过调用构造函数。 JVM不会抱怨它。 (字节码validation仅检查每个构造函数是否调用其超类的构造函数,但不检查构造函数的调用者在new
之后调用哪个构造函数。)
您也可以使用Objenesis库,因此您不需要手动生成字节码。
他的意思是在Java中,总是调用超类的构造函数。 这是通过调用super(…)完成的,如果省略此编译器将为您插入一个。 唯一的exception是一个构造函数调用另一个构造函数。 在这种情况下,其他构造函数应该调用super(…)。
编译器的这种自动代码插入实际上很奇怪。 如果你没有超级(…)调用,并且父类没有没有参数的构造函数,则会导致编译错误。 (对于自动插入的内容,编译错误很奇怪。)
C ++不会为您自动插入。