Python与C++构造函数的区别

14 投票
4 回答
3105 浏览
提问于 2025-04-16 06:07

我最近在学习Python,看到一本很棒的书《Dive into Python》,作者提到过,虽然__init__这个方法通常像构造函数一样工作,但从技术上讲,它并不算是真正的构造函数。

我有两个问题:

  1. C++和Python在创建对象时有什么不同?

  2. 什么才算是构造函数,__init__这个方法又为什么不符合这个标准?

4 个回答

2

在很多其他编程语言中,构造函数负责为正在创建的对象分配空间;而在Python中,这个工作是由一个叫做__new__()的方法来完成的。__init__()只是一个初始化的方法。

3

在Python中,创建一个对象是通过__new__这个方法来完成的,这个方法会生成一个通用的默认对象。然后,__init__方法会对这个对象进行修改。__init__其实就是一个普通的方法,特别的是,它可以被虚拟调用,而从__init__中调用的方法也是虚拟调用。

在C++中,首先会以某种方式为对象分配原始内存,这可以是静态分配、在调用栈上分配、通过operator new动态分配,或者作为另一个对象的一部分。然后,正在实例化的类型的构造函数会将这块原始内存初始化为合适的值。对于一个特定的类,构造函数会自动调用基类和成员的构造函数,因此构造过程是“自下而上”的,先构造各个部分。

C++在构造部分的概念上增加了两点特别重要的语言支持:

  • 如果构造函数失败(比如抛出异常),那么已经成功构造的部分会自动被销毁,并且对象的内存也会自动释放。
  • 在类型T的构造函数执行时,对象的类型是T,所以调用虚拟方法时会被解析为T类型(此时确实是T),而T可以是你实例化的类的基类。

第一点意味着,如果你设计得当,C++类的对象在你手中时是可以直接使用的。如果构造失败,你就根本不会得到一个有效的对象。

此外,C++的规则确保对于每个最派生类T的对象,只有一个T的构造函数调用。我以前称之为单一构造函数调用保证。虽然标准中并没有明确规定这一点,但它确实存在,标准的详细规则就是为了实现这一点(就像你不会找到关于语句以分号结束的单一规则,但各种语法规则共同作用,形成了一个简单的高层规则)。

单一构造函数调用保证、自动清理保证,以及在基类构造函数执行时对象类型的变化,可能是与Python对象构造最重要的三大区别。

还有很多内容可以讨论,但我认为这些是最重要的概念。

祝好!

15

作者提到的区别是,在Python语言中,你在进入__init__之前,就已经有了一个有效的对象。因此,它并不是一个“构造函数”,因为在C++中,构造函数是把一个无效的、未构造的对象变成一个“合格”的对象。

简单来说,Python中的__new__是用来返回“新的对象实例”,而C++中的new操作符只是返回一些内存,这些内存还不是任何类的实例。

不过,在Python中,__init__通常是你第一次设定一些重要的类属性(比如它有哪些属性)。所以对于使用你这个类的人来说,它也可以被看作是一个构造函数。只是Python的运行时并不在乎这些属性是否存在。如果你愿意,可以说它对什么算是构造好的对象的标准很低。

我觉得作者说得有道理,这确实是对Python创建对象方式的一个有趣的看法。不过这之间的区别很细微,我怀疑把__init__称为构造函数会导致代码出错。

另外,我注意到Python的文档中把__init__称作构造函数(http://docs.python.org/release/2.5.2/ref/customization.html)

作为对构造函数的特殊限制,不能返回任何值

...所以如果把__init__看作构造函数有什么实际问题的话,那Python就麻烦了!

Python和C++构造对象的方式有一些相似之处。两者都调用一个相对简单的函数(Python中的__new__用于对象实例,而C++中是某种operator new用于原始内存),然后都调用一个函数来进一步初始化对象,使其处于有用的状态(Python中的__init__和C++中的构造函数)。

实际的区别包括:

  • 在C++中,如果需要,基类的无参构造函数会自动按顺序调用,而在Python中,你必须在自己的__init__中显式地初始化基类。即使在C++中,如果基类构造函数有参数,你也必须指定。

  • 在C++中,有一整套机制来处理构造函数抛出异常时的情况,包括调用已经构造好的子对象的析构函数。而在Python中,我认为运行时最多只会调用__del__

还有一个区别是,__new__不仅仅是分配内存,它必须返回一个实际的对象实例。不过,原始内存这个概念在Python代码中并不太适用。

撰写回答