为什么当你创建一个带参数的构造函数后,无参构造函数就消失了?

169
在C#、C++和Java中,当你创建一个带参数的构造函数时,默认的无参构造函数会被移除。我一直接受这个事实,但现在我开始想知道为什么。
这种行为的原因是什么?它只是一种“安全措施/猜测”,即“如果你已经创建了自己的构造函数,你可能不想让这个隐式的构造函数挂在那里”吗? 还是它有一个技术原因,使得编译器无法在你自己创建构造函数后添加默认构造函数?

7
你可以将C ++添加到具有此行为的语言列表中。 - H H
18
在C++中,现在可以使用Foo() = default;来获取默认值。 - MSalters
1
如果你的带参数构造函数可以为所有参数设置默认值,那么它将与内置的无参构造函数冲突,因此在创建自己的构造函数时需要将其删除。 - Morwenn
3
想象一下参加第一款编译器创始人之间的辩论,讨论焦点是默认构造函数的要求,这将会激发出多么激烈的讨论。 - kingdango
7
C++不仅仅是另一个带有这种行为的编程语言,而是它的创始者,并且旨在允许与C语言兼容,正如Stroustrup在《C++设计与演化》一书中所讨论的,并总结在我的更新答案中。 - Jon Hanna
1
@kingdango,没有激烈的讨论,只有Stroustrup一个人在办公室里编写一些宏。请阅读我在回复Henk时提到的书籍。 - Jon Hanna
11个回答

229

如果您已经添加了自己的构造函数,编译器就没有理由不添加构造函数-编译器可以做任何它想做的事情!然而,你必须看看什么最有意义:

  • 如果我没有为非静态类定义任何构造函数,那么我很可能想要实例化该类。为了允许这个操作,编译器必须添加一个无参数构造函数,它将没有任何影响,只是用来允许实例化。这意味着我不必在我的代码中包含一个空构造函数来使它工作。
  • 如果我定义了自己的构造函数,特别是带有参数的构造函数,则我很可能有自己的逻辑必须在创建类时执行。如果编译器在这种情况下创建一个空的无参构造函数,它将允许某人跳过我编写的逻辑,这可能导致我的代码以各种方式出现问题。如果我在这种情况下想要一个默认的空构造函数,我需要明确地说明。

因此,在每种情况下,您可以看到当前编译器的行为最有意义,以保留代码的可能意图


2
我认为你回答的其余部分基本上证明了你第一句话是错误的。 - Konrad Rudolph
76
第一句话说,在这种情况下编译器 可能 会添加一个构造函数 - 其余的回答解释了为什么它不会(对于问题中指定的语言)。 - JohnL
1
不是这样的。如果我们从头开始设计一个面向对象的语言,没有构造函数最明显的意思是,“你忘记添加一个确保类不变量的构造函数”,这将引发编译错误。 - Jon Hanna

71

当然,技术上并没有理由一定要以这种方式设计语言。

我认为有四个比较现实的选项:

  1. 根本没有默认构造函数
  2. 当前情况
  3. 始终默认提供默认构造函数,但允许显式地禁用它
  4. 始终提供默认构造函数,且不允许其被禁用

选项1有些吸引人,因为我写的代码越多,我就越不需要无参数构造函数。有一天,我应该数一下我到底有多少次真正使用了默认构造函数...

选项2对我来说很好。

选项3违背了Java和C#在其他方面的规则。除了在Java中显式地使某些东西比默认情况更私有之外,你从来没有什么东西是显式地“删除”的。

选项4非常糟糕- 你绝对希望能够强制使用某些参数进行构造。 new FileStream() 甚至意味着什么?

因此,基本上,如果你接受默认构造函数有任何意义的前提,那么我认为一旦你提供自己的构造函数,抑制默认构造函数就有很大意义。


8
如果你的无参构造函数真的什么都不需要做,那它只需要一行代码,肯定不需要比写一个“显式删除”语句更多的代码。 - Jon Skeet
2
@PetrMensik:对不起,我的错,没错。那肯定与我的经历相反 - 我认为自动包含某些东西更加危险......如果您因错误而未排除它,可能会破坏您的约束等。 - Jon Skeet
2
对于C#的结构体,无论好坏,情况都是这样。 - Jay Bazuzi
4
我喜欢选项1,因为它更容易理解。没有在代码中看不到的“神奇”构造函数。使用选项1时,编译器应该会在非静态类没有实例构造函数时发出警告(甚至可能禁止?)。比如:“找不到类<TYPE>的实例构造函数。您是否意味着声明该类为静态类?” - Jeppe Stig Nielsen
1
@JayBazuzi:我认为 .net 结构体没有默认构造函数;相反,我认为它们在没有运行任何构造函数的情况下就存在了。请注意,在 .net 中定义结构体实际上定义了一个值类型和一个类类型,其中从前者到后者有一个隐式(扩展)转换 [称为“装箱”],并且从后者到前者有一个可用的显式(缩小)转换 [称为“拆箱”]。与值类型相关联的类类型确实有一个可用的默认构造函数,以通过反射创建装箱实例。 - supercat
显示剩余15条评论

20

编辑。实际上,尽管我在第一次回答中所说的是有效的,但这才是真正的原因:

最初有C语言。C语言不是面向对象的(你可以采用面向对象的方式,但它并没有帮助你或强制任何事情)。

然后出现了C With Classes,后来更名为C++。C++是面向对象的,因此鼓励封装,并确保对象的不变量 - 在构造时以及任何方法的开头和结尾,对象处于有效状态。

自然而然的做法是强制类始终必须有一个构造函数,以确保它以有效状态开始 - 如果构造函数不必执行任何操作来确保此状态,则空构造函数将记录此事实。

但C++的目标是与C兼容,以至于尽可能多的C程序也是有效的C++程序(这不再是一个活跃的目标,并且C的演进与C++分离意味着它不再适用)。

这种情况的一个影响是structclass之间的功能重复。前者按默认方式执行C的操作(默认公共),后者按良好的OO方式进行操作(默认情况下所有内容都是私有的,开发人员积极公开他们想要公开的内容)。

另一个影响是,为了使C struct在C++中有效,因为C没有构造函数,所以必须有一种C ++方式看待它的含义。因此,虽然没有构造函数会违反积极确保不变性的面向对象实践,但C++认为这意味着有一个默认的无参数构造函数,其行为就像它有一个空的函数体。

现在,所有C structs 都是有效的C++ structs,(这意味着它们与C++ classes 相同,所有成员和继承都是公共的),从外部看起来好像它只有一个无参数构造函数。

如果您在一个classstruct中放置了构造函数,则表示您使用的是C++/OO方式而不是C方式,并且没有必要使用默认构造函数。尽管它作为一种简写方式存在,但即使在不支持兼容性的情况下(它使用其他C++特性而不是C特性),人们仍然继续使用它。因此,当Java出现(在许多方面基于C++)以及后来的C#(以不同的方式基于C++和Java)时,它们将这种方法保留为程序员可能已经习惯的东西。
斯特鲁斯特鲁普在他的《C++程序设计语言》和更加专注于语言“原因”的《C++设计与演化》中都有关于这个问题的阐述。
倘若没有默认构造函数,那么我不需要一个无参数的构造函数,因为我不能为我的类设置有意义的状态。实际上,在C#中,这是可以发生在结构体中的事情(但如果您无法在C#中对全零且空值的结构体进行有意义的使用,则最好使用非公开可见的优化,否则就会在使用结构体时存在设计缺陷)。为了使我的类能够保护其不变式,我需要一个特殊的removeDefaultConstructor关键字。至少,我需要创建一个私有的无参数构造函数,以确保没有调用代码调用默认构造函数。这使语言更加复杂,最好不要这样做。总的来说,最好不要认为添加构造函数是删除默认构造函数,最好将完全没有构造函数视为一种语法糖,用于添加一个不执行任何操作的无参数构造函数。

1
然而,我又看到有人认为这是一个糟糕的回答,因此投了反对票,但却无心启迪我或其他任何人。这可能是有用的,你知道的。 - Jon Hanna
我的回答也是一样的。我这里没有发现任何问题,所以我给一个赞。 - Botz3000
@Botz3000 我不在乎分数,但如果他们有批评意见,我宁愿阅读它。不过,这确实让我想到了一些可以添加到上面的东西。 - Jon Hanna
1
再次遭受没有任何解释的负评。如果我错过了一些如此明显不需要解释的东西,请假设我很蠢并帮我解释一下,谢谢。 - Jon Hanna
+1 我并不是专家,但从历史的角度来看,这对我来说是最有道理的。 - jogojapan
1
@jogojapan 我也不是专家,但做出决定的人写了一本书,所以我不必成为专家。顺便说一句,这是一本非常有趣的书;它涵盖了低级技术的一些内容和许多设计决策,其中有趣的部分包括一个人在演讲中说你应该在提出新功能之前捐赠一个肾脏(这样你会认真考虑,并且只做两次),就在他介绍模板和异常的演讲之前。 - Jon Hanna

13

如果您未采取任何措施来控制对象的创建,则会添加默认的无参构造函数。一旦您创建了一个单独的构造函数来控制,编译器将“后退”,让您完全掌控。

如果不是这样,那么如果您只想通过带参数的构造函数构造对象,就需要一些明确的方法来禁用默认构造函数。


你实际上有这个选项。将无参数构造函数设为私有。 - mw_21
5
不一样。类的任何方法都可以调用它,包括静态方法。我更喜欢它完全不存在。 - Anders Abel

3
我认为问题应该反过来问:如果你没有定义任何其他构造函数,为什么不需要声明默认构造函数?对于非静态类,构造函数是必须的。所以我认为,如果您没有定义任何构造函数,则生成的默认构造函数只是 C# 编译器中方便的功能。此外,如果您的类没有构造函数,则无效。因此,隐式生成一个什么也不做的构造函数没有问题。这肯定比到处都是空构造函数看起来更整洁。
如果您已经定义了一个构造函数,则您的类是有效的,那么编译器为什么要假设您想要一个默认构造函数呢?如果您不想要默认构造函数怎么办?实现一个属性来告诉编译器不要生成默认构造函数?我认为这不是一个好主意。

3
这是编译器的一个方便函数。 如果你定义了有参数的构造函数但没有定义无参数构造函数,则不希望允许无参数构造函数的可能性更大。
对于许多对象来说,使用空构造函数进行初始化就毫无意义。
否则,您需要为每个要限制的类声明一个私有的无参数构造函数。
在我看来,允许一个需要参数才能正常运行的类具有无参数构造函数不是好的编程风格。

1

前提

这种行为可以看作是类具有默认公共无参数构造函数的自然扩展。根据所提出的问题,我们将此决定视为前提,并假设在此情况下不会对其进行质疑。

删除默认构造函数的方法

因此,必须有一种方法来删除默认的公共无参数构造函数。可以通过以下方式实现此删除:

  1. 声明一个非公共无参数构造函数
  2. 当声明带参数的构造函数时,自动删除无参数构造函数
  3. 某些关键字/属性指示编译器删除无参数构造函数(足够尴尬,易于排除)

选择最佳解决方案

现在我们问自己:如果没有无参数构造函数,它必须被替换为什么?在什么类型的场景下,我们想要删除默认的公共无参数构造函数?

事情开始变得清晰。首先,它必须用带参数的构造函数或非公共构造函数替换。其次,您不想要无参数构造函数的情况包括:

  1. 我们不希望该类被实例化,或者我们想要控制构造函数的可见性:声明一个非公共构造函数
  2. 我们想要强制在构造时提供参数:声明一个带参数的构造函数

结论

以上就是C#,C ++和Java允许删除默认公共无参构造函数的两种方法。


结构清晰易于理解,加一分。但是关于上面的第三点:我认为为构造函数删除引入一个特殊的关键字并不是一个尴尬的想法,事实上C++11已经为此引入了= delete - jogojapan

1

我认为这是由编译器处理的。如果您在 ILDASM 中打开 .net 程序集,即使该构造函数不在代码中,您也将看到默认构造函数。如果您定义了一个带参数的构造函数,则默认构造函数将不会被看到。

实际上,当您定义类(非静态)时,编译器提供此功能,以便您只创建一个实例。如果您想要执行任何特定操作,那么您肯定会有自己的构造函数。


1

默认构造函数只能在类没有构造函数时构建。编译器是这样编写的,以提供它作为备用机制。

如果您有一个参数化的构造函数,您可能不希望使用默认构造函数创建对象。如果编译器提供了默认构造函数,您将不得不编写一个无参构造函数并将其设置为私有,以防止使用无参数创建对象。

此外,您很可能会忘记禁用或“私有化”默认构造函数,从而导致潜在的难以捕获的功能错误。

现在,如果您想要通过默认方式或通过传递参数来创建对象,则必须明确定义一个无参构造函数。这是强制检查的,否则编译器会抱怨,从而确保没有漏洞。


0

一个类需要一个构造函数。这是强制性要求。

  • 如果您不创建一个,将自动为您提供无参数的构造函数。
  • 如果您不想要一个无参数的构造函数,则需要创建自己的构造函数。
  • 如果您需要无参数构造函数和基于参数的构造函数,则可以手动添加它们。

我会用另一个问题来回答你,为什么我们总是想要一个默认的无参数构造函数?有时这并不是所期望的,因此开发人员可以根据需要添加或删除它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接