为什么在创建带参数的默认无参数构造函数时会消失


161

在C#,C ++和Java中,当创建带有参数的构造函数时,默认的无参数函数将消失。我一直只是接受这个事实,但是现在我开始怀疑为什么。

这种行为的原因是什么?难道仅仅是“安全措施/猜测”这样的说法:“如果您创建了自己的构造函数,那么您可能不希望这种隐式结构徘徊”?还是出于技术原因,一旦您自己创建了构造函数,编译器就不可能添加一个?


7
您可以将C ++添加到具有这种行为的语言列表中。
Henk Holterman

18
在C ++中,您现在可以说Foo() = default;恢复默认值了。
MSalters

1
如果带参数的构造函数的所有参数都可以具有默认参数,则它将与内置的无参数构造函数冲突,因此在创建自己的无参数构造函数时需要将其删除。
Morwenn 2012年

3
想象一下,在第一个编译器的创建者之间进行的这场辩论中,提出了默认的构造函数要求,这将引发激烈的讨论。
kingdango 2012年

7
@HenkHolterman C ++不仅是这种行为的另一种,而且是创建者,它是允许C兼容的,正如Stroustrup在《 C ++的设计和演进》中讨论的那样,并在我的最新答案中进行了总结。
乔恩·汉娜

Answers:


219

如果您添加了自己的构造器,则没有理由编译器无法添加构造器-编译器可以执行几乎所有需要的操作!但是,您必须查看最有意义的内容:

  • 如果我没有为非静态类定义任何构造函数,那么我很可能希望能够实例化该类。为了做到这一点,编译器必须添加一个无参数的构造函数,该构造函数除了允许实例化之外没有其他作用。这意味着我不必为了使其正常工作而在代码中包含空的构造函数。
  • 如果我定义了自己的构造函数,尤其是带有参数的构造函数,那么我很可能具有自己的逻辑,必须在创建类时执行该逻辑。如果在这种情况下,编译器创建一个空的,无参数的构造函数,它将允许某人跳过我编写的逻辑,这可能导致我的代码以多种方式中断。如果在这种情况下我想使用默认的空构造函数,则需要这样明确地说明。

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


2
我认为您答案的其余部分几乎可以证明您的第一句话是错误的。
康拉德·鲁道夫

76
@KonradRudolph,第一句话说,在这种情况下,编译器可以添加构造函数-其余的答案说明了为什么不这样做(对于问题中指定的语言)
JohnL 2012年

1
好吧 如果我们从头开始设计一种OO语言,那么最明显的含义就是没有构造函数,即“您忽略了添加可确保类保持不变的构造函数”,这将引发编译错误。
乔恩·汉纳

70

有肯定没有技术上的原因,为什么语言被设计成这样。

我可以看到四个有点现实的选择:

  1. 根本没有默认构造函数
  2. 当前场景
  3. 默认情况下始终提供默认构造函数,但允许将其显式删除
  4. 始终提供默认构造函数,不允许对其进行抑制

选项1有点吸引人,因为我编写的代码越多,我真正想要无参数构造函数的频率就越少。有一天,我应该算刚刚多久我其实最终会使用一个默认的构造...

选项2我很好。

对于语言的其余部分,选项3违反了Java和C#的流程。除非明确计数使事情变得比Java默认情况下的私有程度要高,否则您绝不会明确“删除”任何内容。

选项4太可怕了-您绝对希望能够使用某些参数来强制构建。什么会new FileStream()甚至意味着什么?

因此,基本上,如果您接受提供默认构造函数完全有意义的前提,那么我相信在您提供自己的构造函数后立即取消它是很有意义的。


1
我喜欢选项3,因为在写东西时,我需要更多的时候同时拥有两种类型的构造函数,而不仅仅是带有参数的构造函数。因此,我宁愿每天将一些无参数的构造函数设为私有,然后每天编写10次无参数的构造函数。但这可能只是我,我正在写很多系列的课程……
Petr Mensik 2012年

8
@PetrMensik:如果您的无参数构造函数确实真的不需要做任何事情,那它是一种单行代码,肯定不会比“显式删除”语句花费更多的代码。
乔恩·斯基特

2
@PetrMensik:对不起,我的错,是的。这绝对是我的经验相反-我要说的却是包括自动的东西是更危险的选择太...如果你不小心最终不排除它,你可以搞砸了你的不变量等
乔恩斯基特

2
struct无论好坏,#4都是C#的情况。
杰伊·巴祖兹

4
我喜欢选项1,因为它更容易理解。没有在代码中看不到的“魔术”构造函数。使用选项1时,当(或什至不允许)非静态类没有实例构造函数时,编译器应发出警告。怎么样:“没有为类<TYPE>找到实例构造函数。您是要声明类的静态吗?”
杰普·斯蒂格·尼尔森

19

编辑。实际上,虽然我在第一个答案中说的是正确的,但这是真正的原因:

最初有C。C不是面向对象的(您可以采用OO方法,但它无济于事或强制执行任何操作)。

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

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

但是C ++的目标是尽可能地与C兼容,所有有效的C程序也都是有效的C ++程序(不再像目标那样活跃,而C向C ++的演进意味着它不再成立。 )。

这样做的效果之一是struct和之间的功能重复class。前者以C方式进行操作(默认情况下为所有公共),而后者以良好的OO方式(默认情况下为所有私有,而开发人员则主动将其想要公开的内容公开)。

另一个是为了使C struct(由于C没有构造函数而不能具有构造函数)在C ++中有效,因此对于C ++来看待它的方式必须具有一定的意义。因此,尽管没有构造函数会违背OO确保主动确保不变式的做法,但C ++认为这意味着存在一个默认的无参数构造函数,其行为就像它具有一个空主体。

structs现在所有C 都是有效的C ++ structs(这意味着它们与C ++相同,classes并且所有内容-成员和继承-公共)都从外部进行处理,就好像它具有单个无参数的构造函数一样。

但是,如果确实在class或中放置了一个构造函数struct,那么您将以C ++ / OO方式而不是C方式进行操作,则不需要默认的构造函数。

由于它是一种简写方式,因此即使无法实现兼容性,人们仍会继续使用它(它使用了C中没有的其他C ++功能)。

因此,当Java(以多种方式基于C ++)和后来的C#(以多种方式基于C ++和Java)出现时,他们保留了这种方法,因为编码人员可能已经习惯了这种方法。

Stroustrup在他的《 C ++编程语言》中写了这个,甚至更多,在《The Design and Evolution of C ++》中更加关注该语言的“为什么” 。

===原始答案===

假设这没有发生。

假设我不想要无参数构造函数,因为没有一个我就无法将我的类置于有意义的状态。确实,这struct在C#中可能会发生(但是,如果您无法在C#中有意义地使用全零和空值struct,那么您充其量只是在使用非公开可见的优化,否则会有一个使用中的设计缺陷struct)。

为了使我的班级能够保护其不变性,我需要一个特殊的removeDefaultConstructor关键字。至少,我需要创建一个私有的无参数构造函数,以确保没有调用代码调用默认值。

这使语言更加复杂。最好不要这样做。

总而言之,最好不要将添加构造函数视为删除默认值,而最好不要考虑完全不使用构造函数作为添加不执行任何操作的无参数构造函数的语法糖。


1
再一次,我看到有人认为这是一个足够糟糕的答案,可以否决它,但不会被启发给我或其他任何人。您知道哪种方法有用。
乔恩·汉纳

我的回答也一样。好吧,我在这里没有发现任何问题,因此我+1了。
Botz3000

@ Botz3000我不在乎分数,但是如果他们有批评,我宁愿阅读。尽管如此,它的确使我想到了要添加到上面的内容。
乔恩·汉纳

1
再次没有任何解释的不赞成投票。拜托,如果我遗漏了一些明显的东西,不需要解释,那就假设我很愚蠢,无论如何请帮我解释一下。
乔恩·汉纳

1
@jogojapan我也不是专家,但是那个做出决定的人写了这个,所以我不必如此。顺便说一句,这是一本非常有趣的书。在底层技术上很少,而在设计决策上很多,比如一个人的演讲这样有趣的话说,在他提出演讲之前,您应该先捐出肾脏,然后再提出一项新功能(您会非常想,并且只做两次)介绍模板和例外。
乔恩·汉纳

13

如果您自己不做任何事情来控制对象的创建,则会添加默认的无参数构造函数。一旦创建了一个单独的构造函数来控制,编译器就会“退缩”,让您拥有完全的控制权。

如果不是这样,如果只希望对象可以通过带参数的构造函数构造,则需要某种禁用默认构造函数的显式方法。


您实际上有那个选择。将无参数构造函数设为私有。
mw_21

5
不一样 该类的任何方法,包括静态方法,都可以调用它。我更喜欢完全不存在。
安德斯·亚伯

3

这是编译器的便捷功能。如果您定义带参数的构造函数但不定义无参数的构造函数,那么您不想允许无参数的构造函数的可能性会更高。

对于许多没有意义的对象,使用空构造函数进行初始化就是这种情况。

否则,您必须为要限制的每个类声明一个私有的无参数构造函数。

在我看来,对于需要参数起作用的类,允许无参数的构造函数并不是一种好的样式。


3

我认为问题应该是相反的:如果您还没有定义任何其他构造函数,为什么不需要声明默认构造函数?

对于非静态类,构造函数是必需的。
因此,我认为如果您尚未定义任何构造函数,则生成的默认构造函数只是C#编译器的便捷功能,如果没有构造函数,您的类也将无效。因此,隐式生成不执行任何操作的构造函数没有任何问题。与周围空的构造函数相比,它看上去当然更干净。

如果已经定义了构造函数,则您的类是有效的,那么为什么编译器应该假定您想要默认的构造函数?如果您不想要该怎么办?实现一个属性来告诉编译器不要生成该默认构造函数?我认为这不是一个好主意。


1

仅当类没有构造函数时,才可以构造默认构造函数。编译器的编写方式只能将其作为备份机制提供。

如果具有参数化的构造函数,则可能不希望使用默认构造函数创建对象。如果编译器提供了默认的构造函数,则必须编写一个无参数的构造函数并将其私有化,以防止不使用任何参数创建对象。

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

现在,如果您想以默认方式或通过传递参数来创建对象,则必须显式定义一个无参构造函数。对此进行了严格检查,否则编译器会抱怨,从而确保此处没有漏洞。


1

前提

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

删除默认构造函数的方法

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

  1. 声明一个非公共的无参数构造函数
  2. 声明带有参数的构造函数时,自动删除无参数构造函数
  3. 一些关键字/属性,指示编译器删除无参数的构造函数(很笨拙,很容易排除)

选择最佳解决方案

现在我们问自己:如果没有无参数构造函数,则必须用什么代替?并且将我们想要在什么类型的场景删除默认的公共参数构造函数?

事情开始到位。首先,必须将其替换为带有参数的构造函数或非公共构造函数。其次,您不希望使用无参数构造函数的情况是:

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

结论

我们拥有了它-正是C#,C ++和Java允许删除默认的公共无参数构造函数的两种方式。


+1结构清晰且易于遵循。但是,关于上面的(3.):我认为删除构造函数的特殊关键字并不是一个笨拙的想法,实际上C ++ 11就是= delete为此目的而引入的。
jogojapan 2012年

1

我认为这是由编译器处理的。如果在其中打开.net程序集ILDASM,则即使代码中没有它,您也会看到默认的构造函数。如果定义参数化的构造函数,则不会看到默认的构造函数。

实际上,当您定义类(非静态)时,编译器会提供此功能,以为您将创建实例。而且,如果您希望执行任何特定的操作,那么您肯定会拥有自己的构造函数。


0

这是因为当您不定义构造函数时,编译器会自动为您生成一个不带任何参数的构造函数。当您希望从构造函数中获得更多东西时,您可以重写它。这不是函数重载。因此,编译器现在看到的唯一构造函数是带有参数的构造函数。为了解决此问题,如果不使用构造函数,则可以传递默认值。


0

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

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

我将用另一个答案回答您,为什么我们总是要使用默认的无参数构造函数?在某些情况下,这是不希望的,因此开发人员可以根据需要控制添加或删除它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.