根据John C. Mitchell的说法 - 编程语言中的概念,
[...] Java保证a 无论什么时候调用构造函数 对象已创建。 [...]
这被指向Java的特性,使其在行为上与C ++不同。所以我必须争辩说,在某些情况下,即使创建了该类的对象,C ++也不会为类调用任何构造函数。
我认为这种情况发生在继承发生时,但我无法找出这种情况的例子。
你知道任何一个例子吗?
答案 0 :(得分:14)
如果您的类定义了至少一个构造函数,那么该语言将不允许您在不调用构造函数的情况下构造该类型的对象。
如果您的类没有定义构造函数,那么一般规则是将调用编译器生成的默认构造函数。
正如其他海报所提到的,如果你的类是POD类型,有些情况下你的对象将保持未初始化状态。但这不是因为编译器“没有调用构造函数”。这是因为类型没有构造函数(或者它没有什么构造函数),并且特别处理。但话说回来,Java中不存在POD类型,因此无法真正进行比较。
例如,分配
char
的缓冲区,获取指向第一个char的指针并将其强制转换为对象类型。当然,大多数情况下都是未定义的行为,因此它不是真的“允许”,但编译器通常不会抱怨。
但最重要的是,任何提出类似这些声明但没有明确说明他们所指的特定角落案例的书籍很可能充满了垃圾。然后,大多数写C ++的人实际上并不太了解这种语言,所以这不应该是一个惊喜。
答案 1 :(得分:9)
在Java中有两种情况(我不知道更多),其中可以在不调用构造函数的情况下构造类',而不会导致在C或类似情况下入侵:
Object.clone
时。因此,总是在Java中调用构造函数的说法是错误的。
答案 2 :(得分:4)
对于声明构造函数的C ++类型,如果不使用构造函数,则无法创建这些类型的实例。例如:
class A {
public:
A( int x ) : n( x ) {}
private:
int n;
};
在不使用A(int)构造函数的情况下创建A的instancev是不可能的,除非通过复制,在这个实例中将使用合成的复制构造函数。在任何一种情况下,都必须使用构造函数。
答案 3 :(得分:3)
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 };
在这两种情况下都没有调用构造函数,甚至没有生成默认构造函数:
但是,如果SimpleClass本身定义了一个构造函数,SimpleClass将不再是POD,并且总是会调用其中一个构造函数。
答案 4 :(得分:2)
在C ++中,当实例化对象时,必须调用该类的构造函数。
答案 5 :(得分:2)
在C ++中有一些特殊情况,其中不会调用构造函数。特别是对于POD类型,在某些情况下不会调用隐式定义的默认构造函数。
struct X {
int x;
};
int main() {
X x; // implicit default constructor not called
// No guarantee in the value of x.x
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的兼容性而完成的。
(正如Neil所说)
我认为这种情况发生在继承发生时,但我无法找出这种情况的例子。
这与继承无关,但与实例化的对象类型无关。
答案 6 :(得分:2)
Java实际上可以在没有(!)调用任何构造函数的情况下分配对象。
如果你浏览ObjectInputStream
的来源,你会发现它分配了反序列化的对象,而没有调用任何构造函数。
允许您这样做的方法不是公共API的一部分,而是在sun.*
包中。但是,请不要告诉我它不是语言的一部分。您可以使用公共API执行的操作是将反序列化对象的字节流放在一起,读取它并在那里使用从未调用过构造函数的对象实例!
答案 7 :(得分:1)
只有当你重载新的操作符函数时,才会调用构造函数(它用于避免构造函数调用),否则它的标准是在创建对象时调用构造函数。
void * operator new ( size_t size )
{
void *p = malloc(size);
if(p)
return p;
else
cout<<endl<<"mem alloc failed";
}
class X
{
int X;
};
int main()
{
X *pX;
pX = reinterpret_cast<X *>(operator new(sizeof(X)*5)); // no ctor called
}
答案 8 :(得分:1)
给出一个解释,我有一个建议,为什么作者说对于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 ++程序,它使用未经检查的类型转换形式来避免构造函数调用。在这种情况下,x未初始化,因此我们可能会预期会出现不可预测的行为。
但是,可能还有其他情况,Java也无法应用此规则,即使您确定已经以某种方式构造了对象,提及序列化对象也是完全合理和正确的(除非您当然,对序列化编码做一些黑客攻击。)
答案 9 :(得分:0)
据我所知,Meyers在他的“Effective C ++”中说,只有在控制流程达到构造函数结束时才会创建该对象。否则它不是一个对象。每当你想为一个实际的对象虐待一些原始内存时,你可以这样做:
class SomeClass
{
int Foo, int Bar;
};
SomeClass* createButNotConstruct()
{
char * ptrMem = new char[ sizeof(SomeClass) ];
return reinterpret_cast<SomeClass*>(ptrMem);
}
你不会在这里遇到任何构造函数,但你可能会想,你正在操作一个新创建的对象(并且有很好的时间调试它);
答案 10 :(得分:0)
尝试清楚地了解C ++。答案中有很多不精确的陈述。
在C ++中,POD和类的行为方式相同。始终调用构造函数。对于POD,默认构造函数不执行任何操作:它不会初始化值。但是说没有调用构造函数是错误的。
即使继承,也会调用构造函数。
class A {
public: A() {}
};
class B: public A {
public: B() {} // Even if not explicitely stated, class A constructor WILL be called!
};
答案 11 :(得分:0)
这似乎归结为定义术语“对象”,因此该陈述是重言式。具体来说,就Java而言,他显然将“对象”定义为一个类的实例。关于C ++,他(显然)使用更广泛的对象定义,包括甚至没有构造函数的原始类型。
然而,无论他的定义如何,C ++和Java在这方面都比不同。两者都有原始类型,甚至没有构造函数。两者都支持创建用户定义的类型,以保证在创建对象时调用构造函数。
C ++还支持用户定义类型的创建(在非常特定的限制内),这些类型不一定在所有可能的情况下调用构造函数。但是,对此有严格的限制。其中之一是构造函数必须是“微不足道的” - 即它必须是一个构造函数,它不执行由编译器自动合成的任何内容。
换句话说,如果您使用构造函数编写类,则编译器保证在适当的时间使用它(例如,如果您编写复制构造函数,则将使用您的复制构造函数创建所有副本)。如果编写默认构造函数,编译器将使用它来生成没有提供初始化程序的所有类型的对象,依此类推。
答案 12 :(得分:0)
即使我们使用静态分配的内存缓冲区来创建对象,也会调用构造函数。
可以在以下代码段中看到。 我还没有看到任何一般情况下没有调用构造函数,但是有很多东西要看:)
使用namespace std;
class Object
{
公共:
对象();
〜对象();
};
内联Object :: Object()
{
cout&lt;&lt; “构造\ n” 个;
};
inline Object :: ~Object()
{
cout&lt;&lt; “析构\ n” 个;
};
int main()
{
char buffer [2 * sizeof(Object)];
Object * obj = new(buffer)Object; // placement new,1st object
new(buffer + sizeof(Object))对象; //贴装新的第二个对象
//删除obj; //不要这样做
OBJ [0]〜对象(); //销毁第一个对象
OBJ [1]〜对象(); //销毁第二个对象
}
答案 13 :(得分:0)
在Java中,有些情况下构造函数不被调用。
例如,当反序列化类时,将调用类型层次结构中第一个非可序列化类的默认构造函数,但不调用当前类的构造函数。同样Object.clone
也避免调用构造函数。您也可以生成字节码并自己完成。
要了解这是如何可能的,即使JRE中没有本机代码魔法,也只需查看Java字节码即可。在Java代码中使用new
关键字时,会从中生成两个字节码指令 - 首先使用new
指令分配实例,然后使用invokespecial
指令调用构造函数。
如果您生成自己的字节码(例如使用ASM),则可以更改invokespecial
指令以调用实际类型的超类构造函数之一的构造函数(例如java.lang.Object
)或甚至完全跳过调用构造函数。 JVM不会抱怨它。 (字节码验证仅检查每个构造函数调用其超类的构造函数,但不检查构造函数的调用者在new
之后调用哪个构造函数。)
您也可以使用Objenesis库,因此您无需手动生成字节码。
答案 14 :(得分:-1)
他的意思是在Java中,总是调用超类的构造函数。这是通过调用super(...)完成的,如果省略此编译器将为您插入一个。唯一的异常是一个构造函数调用另一个构造函数。在这种情况下,其他构造函数应该调用super(...)。
编译器自动插入代码实际上很奇怪。如果你没有super(...)调用,并且父类没有没有参数的构造函数,则会导致编译错误。 (对于自动插入的内容,编译错误很奇怪。)
C ++不会为您自动插入。