C++ 与 Java 构造函数
Posted
技术标签:
【中文标题】C++ 与 Java 构造函数【英文标题】:C++ vs Java constructors 【发布时间】:2009-12-08 09:33:24 【问题描述】:根据 John C. Mitchell - 编程语言的概念,
[...] Java 保证一个 每当一个 对象被创建。 [...]
这被认为是 Java 的一个特性,使其在行为上与 C++ 不同。所以我必须争辩说,在某些情况下,即使创建了该类的对象,C++ 也不会为该类调用任何构造函数。
我认为发生继承时会发生这种情况,但我无法找出这种情况的示例。
你知道任何例子吗?
【问题讨论】:
如果这本书真的这么说,我会扔掉这本书。但也许您应该提供逐字引用。 语言中有一些极端情况。特别是,对于所有没有用户定义构造函数的类类型,编译器将生成“隐式定义的默认构造函数”。在某些情况下不会调用该构造函数,首先是声明 POD 自动变量,当 POD 是仅隐式声明构造函数的类的一部分时。 这本书只是陈述引用的句子,然后解释为什么 Java 能保证这一点。它并没有为此谈论 C++,而是展示了使其优于 C++ 的 Java 特性。也许 POD 是正确的答案,但这很奇怪,因为它涉及 C 的遗产,而不是完全面向对象的范式(即结构不是类)。 作为所提供报价的反例,我可以想到至少一个示例,其中将实例化 Java 类但不调用其构造函数 - IIRC,当使用序列化 API 反序列化时类已分配,其成员由 JVM 设置,但没有调用构造函数。 【参考方案1】:如果您的类至少定义了一个构造函数,那么该语言将不允许您在不调用构造函数的情况下构造该类型的对象。
如果你的类没有定义构造函数,那么一般规则是编译器生成的默认构造函数会被调用。
正如其他海报所提到的,如果您的类是 POD 类型,在某些情况下您的对象将未初始化。但这并不是因为编译器“没有调用构造函数”。这是因为类型 没有 没有构造函数(或者它有一个什么都不做的构造函数),并且有些特殊处理。但是话说回来,Java 中不存在 POD 类型,因此无法真正进行比较。
您可以也可以修改一些东西,这样构造函数就不会被调用。例如,分配char
的缓冲区,获取指向第一个字符的指针并将其转换为对象类型。当然,在大多数情况下是未定义的行为,因此它不是真正“允许”的,但编译器通常不会抱怨。
但最重要的是,任何一本书如果提出这样的声明而没有非常明确说明他们所指的是哪些特定的极端情况,那么很可能充满了垃圾。话又说回来,大多数写 C++ 的人实际上对这门语言了解不多,所以这不足为奇。
【讨论】:
我同意,即使在您的“hack around”描述中,我也不会说您已经 创建 一个对象(除非该对象没有,或者是微不足道的构造函数)。虽然您已经获得了对象的存储空间,但对象的生命周期在其构造函数调用完成之前不会开始,而且我不确定我是否同意任何声称如果对象的生命周期尚未开始就创建了对象的人. @Charles:是的,绝对同意。你还没有创建一个对象。但是您确实有一个可以用来假装的指针……假设您不介意一大堆未定义的行为。 ;)【参考方案2】:在 Java 中有两种情况(我不知道更多),可以在不调用其构造函数的情况下构造一个类,而不会导致 C 或类似的黑客攻击:
在反序列化期间,可序列化类不会调用其构造函数。最衍生的不可序列化类的无参数构造函数由序列化机制调用(在 Sun 实现中,通过不可验证的字节码)。 当邪恶Object.clone
被使用时。
因此,在 Java 中总是调用构造函数的说法是错误的。
【讨论】:
我看不出这个答案与 C++ 有什么关系。 (尼尔:我看不出你的回答与 Java 有什么关系……) 我认为这个问题对 C++ 行为的要求相当明确。 重点是引用是错误的——在 Java 中有些情况下创建对象时没有调用构造函数。 在这种情况下,答案应该清楚地表明它实际上是在谈论 Java,而不是 C++。 :)【参考方案3】:对于声明构造函数的 C++ 类型,不使用构造函数就无法创建这些类型的实例。例如:
class A
public:
A( int x ) : n( x )
private:
int n;
;
在不使用 A(int) 构造函数的情况下创建 A 的 instancev 是不可能的,除非通过复制,在此实例中将使用合成的复制构造函数。无论哪种情况,都必须使用构造函数。
【讨论】:
【参考方案4】: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 ;
在这两种情况下都没有调用构造函数,甚至没有生成的默认构造函数:
在没有初始化程序的情况下声明的非常量 POD 对象具有“不确定的初始值”。 POD 对象的默认初始化是零初始化。 (http://www.fnal.gov/docs/working-groups/fpcltf/Pkg/ISOcxx/doc/POD.html)但是,如果 SimpleClass 本身定义了一个构造函数,那么 SimpleClass 将不再是 POD,并且将始终调用其中一个构造函数。
【讨论】:
另一方面,您不能初始化具有该语法的构造函数的类的实例。 它并没有解决问题。 POD 确实有构造函数(隐式默认构造函数)只是编译器不会自动调用它,但你可以自己做:void f( POD const & ); int main() f( POD() );
将创建一个临时默认构造函数(所有字段初始化为0) 并将其传递给 f。 (假设实际定义了 POD 并且是 POD 类型:))【参考方案5】:
在 C++ 中,当实例化一个对象时,必须调用该类的构造函数。
【讨论】:
这里唯一正确的答案,据我所知!这些人到底是什么人??? 不一定。对于 POD 类型,不会调用隐式默认构造函数,即使你可以自己调用。 @dribeas,我解释了提到的“对象”。如果您显式调用 POD 类型的构造函数,它将被调用。例如。 int a = int(); a 将被初始化为 0。 尼尔,我的猜测是:Java 人 ;) 如果您看到使用 object as storage location 的定义而不是 object 作为类的实例。【参考方案6】:在 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 兼容。
(当尼尔出来时)
我认为发生继承时会发生这种情况,但我无法找出这种情况的示例。
这与继承无关,而与被实例化的对象的类型有关。
【讨论】:
而且我认为您的回答虽然本身是正确的,但却使事情变得混乱,并没有解决基本问题。 我认为它确实解决了一个事实:在 C++ 中存在不调用构造函数的情况。还是我错过了这个问题?也许我应该更清楚一点,这与继承无关? 我认为 OP 主要考虑的是用户定义的构造函数。如果其中之一存在,它将被调用。无论如何,编译器生成的 POD 类型有点特殊。 +1。该问题需要创建对象但未调用构造函数的示例。就是这样一个例子。不知道为什么尼尔说它没有回答这个问题。除非基本问题是“Java 是否比 C++ 更好?哈哈”。在这种情况下,Neil 的观察确实应该回答该类的作者可以防止这种情况在 C++ 中发生。 对不起这个愚蠢的问题:POD 是否被视为对象,因为对于 OO 范式,我认为它们不是:至少它们缺乏抽象和子类型。【参考方案7】:Java 实际上可以分配对象而不用(!)调用任何构造函数。
如果您浏览ObjectInputStream
的源代码,您会发现它分配了反序列化的对象 没有调用任何构造函数。
允许您这样做的方法不是公共 API 的一部分,它位于 sun.*
包中。但是,请不要告诉我它不是语言的一部分,因此。您可以使用公共 API 将反序列化对象的字节流放在一起,将其读入,然后您就可以使用从未调用过构造函数的对象实例!
【讨论】:
【参考方案8】:给出一个解释,我有一个关于为什么作者对 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】:只有当你重载 new 操作符函数时,构造函数才不会被调用(它用来避免构造函数调用),否则它的标准构造函数是在创建对象时调用的。
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
【讨论】:
该示例实际上只是使用 operator new 分配内存,然后强制转换为对象指针。这会误导 IMO,因为它并不能真正算作创建对象。 与问题无关。你也可以说 malloc( sizeof(X) *5)。 另外,您不必重载 operator new 来执行此操作。你可以使用 malloc 来达到同样的效果。 ya leiz 你是对的,但我只想说默认的 new 运算符分配内存并调用 ctors,你可以通过使用 operator new 函数来覆盖此行为,该函数又只是分配内存。 @unknown 如果你为一个类重写 new,你可以让它做任何你想做的事情,包括总是返回 NULL。然而,这与对象构造无关。【参考方案10】:据我记得,Meyers 在他的“Effective C++”中说,只有在控制流到达构造函数的末尾时才创建对象。否则它不是一个对象。每当您想为实际对象滥用一些原始内存时,都可以这样做:
class SomeClass
int Foo, int Bar;
;
SomeClass* createButNotConstruct()
char * ptrMem = new char[ sizeof(SomeClass) ];
return reinterpret_cast<SomeClass*>(ptrMem);
您不会在这里遇到任何构造函数,但您可能会认为,您正在操作一个新创建的对象(并且很高兴调试它);
【讨论】:
您不会创建 SomeClass,您只需分配一些内存,其大小恰好与 SomeCleass 的大小相同。 你是对的。我认为我没有提到我在任何地方创建了 SomeClass 对象...如果您阅读了整个答案,当然不仅仅是代码...【参考方案11】:试图让 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!
;
【讨论】:
并非如此。struct X int x; ; void f( X const & ); int main() X x1; f( X() );
实例 x1
已创建,但未调用构造函数。 f()
使用一个临时调用构造函数。但也许我错了,我很难消化标准的那部分......
是的,struct X
构造函数被调用,例如 x1
。但是struct X
构造函数是一个默认构造函数,什么都不做。【参考方案12】:
这似乎归结为定义术语“对象”,以便该语句是重言式。具体来说,关于 Java,他显然将“对象”定义为类的实例。对于 C++,他(显然)使用了更广泛的对象定义,其中包括甚至没有构造函数的基本类型。
然而,不管他的定义如何,C++ 和 Java 在这方面的相似之处多于不同之处。两者都有甚至没有构造函数的原始类型。两者都支持创建用户定义类型,以保证在创建对象时调用构造函数。
C++ 还支持创建(在非常具体的限制内)用户定义的类型,这些类型不一定在所有可能的情况下都调用构造函数。但是,对此有严格的限制。其中之一是构造函数必须是“微不足道的”——即它必须是编译器自动合成的不执行任何操作的构造函数。
换句话说,如果你编写一个带有构造函数的类,编译器可以保证在正确的时间使用它(例如,如果你编写了一个复制构造函数,所有的副本都将使用你的复制构造函数进行)。如果您编写了默认构造函数,编译器将使用它来生成该类型的所有未提供初始化程序的对象,依此类推。
【讨论】:
【参考方案13】:即使我们使用静态分配的内存缓冲区来创建对象,也会调用构造函数。
可以在下面的代码sn -p中看到。 我还没有看到任何不调用构造函数的一般情况,但是还有很多东西要看:)
包括
使用命名空间标准;
类对象
公开:
对象();
~对象();
;
内联对象::Object()
cout
;
内联对象::~Object()
cout
;
int main()
字符缓冲区[2 * sizeof(Object)];
Object *obj = new(buffer) Object; // 放置新的第一个对象
new(buffer + sizeof(Object)) 对象; // 放置新的第二个对象
// 删除 obj; // 不要这样做
obj[0].~Object(); // 销毁第一个对象
obj[1].~Object(); // 销毁第二个对象
【讨论】:
【参考方案14】:在Java中,有些情况下构造函数不被调用。
例如当一个类被反序列化时,会调用类型层次中第一个不可序列化类的默认构造函数,但不会调用当前类的构造函数。 Object.clone
也避免调用构造函数。也可以自己生成字节码。
要了解这是如何实现的,即使在 JRE 中没有原生代码魔法,只要看看 Java 字节码。在 Java 代码中使用 new
关键字时,会从中生成两条字节码指令 - 首先使用 new
指令分配实例,然后使用 invokespecial
指令调用构造函数。
如果您生成自己的字节码(例如使用 ASM),则可以更改 invokespecial
指令以调用实际类型的超类的构造函数之一的构造函数(例如 java.lang.Object
),甚至完全跳过调用构造函数。 JVM 不会抱怨它。 (字节码验证只检查每个构造函数是否调用了其超类的构造函数,但不检查构造函数的调用者在new
之后调用了哪个构造函数。)
你也可以使用Objenesis库,这样你就不需要手动生成字节码了。
【讨论】:
【参考方案15】:他的意思是在Java中,总是调用超类的构造函数。这是通过调用 super(...) 来完成的,如果你省略这个编译器会为你插入一个。唯一的例外是一个构造函数调用另一个构造函数。在这种情况下,其他构造函数应该调用 super(...)。
编译器的这种自动代码插入实际上很奇怪。如果您没有 super(...) 调用,并且父类没有没有参数的构造函数,则会导致编译错误。 (自动插入的东西出现编译错误很奇怪。)
C++ 不会为您自动插入。
【讨论】:
收回我的upmod已经太晚了。尼尔是正确的。以上是关于C++ 与 Java 构造函数的主要内容,如果未能解决你的问题,请参考以下文章