Pourquoi le constructeur sans paramètre par défaut disparaît-il lorsque vous en créez un avec des paramètres?

161

En C #, C ++ et Java, lorsque vous créez un constructeur prenant des paramètres, celui sans paramètre par défaut disparaît. J'ai toujours juste accepté ce fait, mais maintenant j'ai commencé à me demander pourquoi.

Quelle est la raison de ce comportement? Est-ce juste une "mesure de sécurité / supposition" disant "Si vous avez créé votre propre constructeur, vous ne voulez probablement pas que celui-ci traîne"? Ou y a-t-il une raison technique qui empêche le compilateur d'en ajouter un une fois que vous avez vous-même créé un constructeur?

Olagjo
la source
7
Vous pouvez ajouter C ++ à la liste des langages qui ont ce comportement.
Henk Holterman
18
Et en C ++, vous pouvez maintenant dire Foo() = default;de récupérer celui par défaut.
MSalters
1
Si votre constructeur avec des paramètres peut avoir des arguments par défaut pour tous ses paramètres, alors il entrerait en conflit avec le constructeur sans paramètre intégré, d'où la nécessité de le supprimer lorsque vous créez le vôtre.
Morwenn
3
Imaginez être présent à ce débat entre les fondateurs du premier compilateur pour rendre l'exigence du constructeur par défaut et la discussion animée qu'il aurait inspirée.
kingdango
7
@HenkHolterman C ++ n'est pas simplement un autre avec ce comportement, mais le créateur, et c'est pour permettre la compatibilité C, comme discuté par Stroustrup dans The Design and Evolution of C ++ et comme résumé dans ma réponse mise à jour.
Jon Hanna

Réponses:

219

Il n'y a aucune raison pour que le compilateur ne puisse pas ajouter le constructeur si vous avez ajouté le vôtre - le compilateur pourrait faire à peu près tout ce qu'il veut! Cependant, vous devez regarder ce qui a le plus de sens:

  • Si je n'ai défini aucun constructeur pour une classe non statique, je souhaite probablement pouvoir instancier cette classe. Pour permettre cela, le compilateur doit ajouter un constructeur sans paramètre, qui n'aura aucun effet mais pour permettre l'instanciation. Cela signifie que je n'ai pas besoin d'inclure un constructeur vide dans mon code juste pour le faire fonctionner.
  • Si j'ai défini mon propre constructeur, en particulier un avec des paramètres, alors j'ai probablement ma propre logique qui doit être exécutée lors de la création de la classe. Si le compilateur devait créer un constructeur vide et sans paramètre dans ce cas, cela permettrait à quelqu'un d' ignorer la logique que j'avais écrite, ce qui pourrait conduire à la rupture de mon code de plusieurs façons. Si je veux un constructeur vide par défaut dans ce cas, je dois le dire explicitement.

Ainsi, dans chaque cas, vous pouvez voir que le comportement des compilateurs actuels est le plus logique en termes de préservation de l' intention probable du code.

Dan Puzey
la source
2
Je pense que le reste de votre réponse prouve à peu près votre première phrase erronée.
Konrad Rudolph
76
@KonradRudolph, la première phrase dit qu'un compilateur pourrait ajouter un constructeur dans ce scénario - le reste de la réponse explique pourquoi ce n'est pas le cas (pour les langues spécifiées dans la question)
JohnL
1
Et bien non. Si nous avons conçu un langage OO à partir de zéro, la signification la plus évidente de l'absence de constructeur est: "vous avez négligé d'ajouter un constructeur qui garantit l'invariant de la classe" et cela déclencherait une erreur de compilation.
Jon Hanna
70

Il n'y a certainement aucune raison technique pour laquelle le langage doit être conçu de cette façon.

Il y a quatre options quelque peu réalistes que je peux voir:

  1. Aucun constructeur par défaut du tout
  2. Le scénario actuel
  3. Fournit toujours un constructeur par défaut par défaut, mais lui permettant d'être explicitement supprimé
  4. Toujours fournir un constructeur par défaut sans autoriser sa suppression

L'option 1 est quelque peu attrayante, en ce sens que plus je code, moins je veux vraiment d'un constructeur sans paramètre. Un jour, je devrais compter à quelle fréquence je finis par utiliser un constructeur par défaut ...

Option 2 Je suis d'accord.

L'option 3 va à l'encontre du flux de Java et de C #, pour le reste du langage. Il n'y a jamais rien que vous «supprimiez» explicitement, à moins que vous ne comptiez explicitement rendre les choses plus privées qu'elles ne le seraient par défaut en Java.

L'option 4 est horrible - vous voulez absolument pouvoir forcer la construction avec certains paramètres. Que new FileStream()signifierait même?

Donc, fondamentalement, si vous acceptez la prémisse selon laquelle fournir un constructeur par défaut a du sens, je pense qu'il est très logique de le supprimer dès que vous fournissez votre propre constructeur.

Jon Skeet
la source
1
J'aime l'option 3, car lorsque j'écris quelque chose, j'ai besoin plus souvent d'avoir les deux types de constructeurs, puis juste un constructeur avec paramètre. Je préférerais donc une fois par jour rendre privé un constructeur sans paramètre, puis écrire 10 fois par jour des constructeurs sans paramètre. Mais c'est probablement juste moi, j'écris beaucoup de classes sérieuses ...
Petr Mensik
8
@PetrMensik: Si votre constructeur sans paramètre n'a vraiment besoin de rien faire, c'est un one-liner qui ne prendrait sûrement pas beaucoup plus de code qu'une instruction de "suppression explicite".
Jon Skeet
2
@PetrMensik: Désolé, mon erreur, oui. C'est certainement le contraire de mon expérience - et je dirais qu'inclure quelque chose automatiquement est aussi l' option la plus dangereuse ... si vous finissez accidentellement par ne pas l'exclure, vous pouvez bousiller vos invariants, etc.
Jon Skeet
2
# 4 est le cas avec les C # struct, pour le meilleur ou pour le pire.
Jay Bazuzi
4
J'aime l'option 1 parce qu'elle est plus facile à comprendre. Pas de constructeur "magique" que vous ne pouvez pas voir dans le code. Avec l'option 1, le compilateur devrait émettre un avertissement quand (ou peut-être même interdire?) Une classe non statique n'a pas de constructeur d'instance. Que diriez-vous: "Aucun constructeur d'instance trouvé pour la classe <TYPE>. Vouliez-vous déclarer la classe statique?"
Jeppe Stig Nielsen
19

Éditer. En fait, bien que ce que je dis dans ma première réponse soit valable, c'est la vraie raison:

Au début, il y avait C. C n'est pas orienté objet (vous pouvez adopter une approche OO, mais cela ne vous aide pas ou n'applique rien).

Ensuite, il y a eu C With Classes, qui a ensuite été renommé C ++. Le C ++ est orienté objet, et encourage donc l'encapsulation, et garantit l'invariant d'un objet - lors de la construction et au début et à la fin de toute méthode, l'objet est dans un état valide.

La chose naturelle à faire avec cela, est de faire en sorte qu'une classe doit toujours avoir un constructeur pour s'assurer qu'elle démarre dans un état valide - si le constructeur n'a rien à faire pour garantir cela, alors le constructeur vide documentera ce fait .

Mais un objectif avec C ++ était d'être compatible avec C au point que, dans la mesure du possible, tous les programmes C valides étaient également des programmes C ++ valides (ce n'est plus un objectif aussi actif, et l'évolution du C séparée vers C ++ signifie qu'il ne tient plus) ).

Un effet de ceci était la duplication des fonctionnalités entre structet class. Le premier fait les choses à la manière C (tout est public par défaut) et le second fait les choses d'une bonne manière OO (tout est privé par défaut, le développeur rend activement public ce qu'il veut).

Un autre est que pour qu'un C struct, qui ne pouvait pas avoir de constructeur parce que C n'a pas de constructeurs, soit valide en C ++, alors il devait y avoir une signification pour cela à la manière C ++ de le voir. Et donc, bien que ne pas avoir de constructeur irait à l'encontre de la pratique OO d'assurer activement un invariant, C ++ a pris cela pour signifier qu'il y avait un constructeur sans paramètre par défaut qui agissait comme s'il avait un corps vide.

Tous les C structsétaient désormais des C ++ valides structs, (ce qui signifiait qu'ils étaient identiques à C ++ classesavec tout - membres et héritage - public) traités de l'extérieur comme s'il avait un seul constructeur sans paramètre.

Si toutefois vous avez mis un constructeur dans un classou struct, alors vous faisiez les choses de la manière C ++ / OO plutôt que de la manière C, et il n'y avait pas besoin d'un constructeur par défaut.

Puisqu'il servait de raccourci, les gens continuaient à l'utiliser même lorsque la compatibilité n'était pas possible autrement (il utilisait d'autres fonctionnalités C ++ pas en C).

Par conséquent, lorsque Java est arrivé (basé sur C ++ à bien des égards) et plus tard C # (basé sur C ++ et Java de différentes manières), ils ont conservé cette approche car les codeurs étaient peut-être déjà habitués.

Stroustrup écrit à ce sujet dans son The C ++ Programming Language et plus encore, avec plus d'attention sur les «pourquoi» du langage dans The Design and Evolution of C ++ .

=== Réponse originale ===

Disons que cela ne s'est pas produit.

Disons que je ne veux pas d'un constructeur sans paramètre, car je ne peux pas mettre ma classe dans un état significatif sans un. En effet, c'est quelque chose qui peut arriver avec structen C # (mais si vous ne pouvez pas utiliser de manière significative un tous les zéros et les nuls structen C #, vous utilisez au mieux une optimisation non visible publiquement, et sinon défaut de conception lors de l'utilisation struct).

Pour rendre ma classe capable de protéger ses invariants, j'ai besoin d'un removeDefaultConstructormot-clé spécial . À tout le moins, je devrais créer un constructeur privé sans paramètre pour m'assurer qu'aucun code d'appel n'appelle la valeur par défaut.

Ce qui complique encore la langue. Mieux vaut ne pas le faire.

Dans l'ensemble, il vaut mieux ne pas penser à ajouter un constructeur comme supprimant la valeur par défaut, mieux vaut penser à ne pas avoir de constructeur du tout comme du sucre syntaxique pour ajouter un constructeur sans paramètre qui ne fait rien.

Jon Hanna
la source
1
Et encore une fois, je vois que quelqu'un pense que c'est une réponse suffisamment mauvaise pour la rejeter, mais cela ne pourrait pas être dérangé de m'éclairer, moi ou quelqu'un d'autre. Ce qui pourrait être, vous savez, utile.
Jon Hanna
Même chose sur ma réponse. Eh bien, je ne vois rien de mal ici, donc +1 de moi.
Botz3000
@ Botz3000 Je me fiche du score, mais s'ils ont une critique, je préfère la lire. Pourtant, cela m'a fait penser à quelque chose à ajouter à ce qui précède.
Jon Hanna
1
Et encore une fois avec le vote négatif sans aucune explication. S'il vous plaît, si je manque quelque chose de si évident qu'il ne nécessite pas d'explication, supposez simplement que je suis stupide et faites-moi la faveur de l'expliquer quand même.
Jon Hanna
1
@jogojapan Je ne suis pas non plus un expert, mais le gars qui est - en étant le gars qui a pris la décision - a écrit à ce sujet, donc je n'ai pas à l'être. Soit dit en passant, c'est un livre très intéressant; peu de technologie de bas niveau et beaucoup de décisions de conception, avec des éléments amusants comme le discours d'une personne disant que vous devriez avoir à donner un rein avant de proposer une nouvelle fonctionnalité (vous réfléchissez très fort, et ne le faites que deux fois) juste avant son discours introduire à la fois des modèles et des exceptions.
Jon Hanna
13

Le constructeur sans paramètre par défaut est ajouté si vous ne faites rien vous-même pour prendre le contrôle de la création d'objet. Une fois que vous avez créé un seul constructeur pour prendre le contrôle, le compilateur «recule» et vous laisse le contrôle total.

Si ce n'était pas le cas, vous auriez besoin d'un moyen explicite de désactiver le constructeur par défaut si vous voulez uniquement que les objets soient constructibles via un constructeur avec des paramètres.

Anders Abel
la source
Vous avez en fait cette option. Rendre le constructeur sans paramètre privé.
mw_21
5
Ce n'est pas pareil. Toute méthode de la classe, y compris les méthodes statiques, peut l'appeler. Je préfère l'avoir totalement inexistant.
Anders Abel
3

C'est une fonction pratique du compilateur. Si vous définissez un constructeur avec des paramètres mais ne définissez pas un constructeur sans paramètre, la possibilité que vous ne souhaitiez pas autoriser un constructeur sans paramètre est beaucoup plus élevée.

C'est le cas de nombreux objets qui n'ont tout simplement pas de sens à s'initialiser avec un constructeur vide.

Sinon, vous devrez déclarer un constructeur privé sans paramètre pour chaque classe que vous souhaitez restreindre.

À mon avis, ce n'est pas un bon style d'autoriser un constructeur sans paramètre pour une classe qui a besoin de paramètres pour fonctionner.

mw_21
la source
3

Je pense que la question devrait être l'inverse: pourquoi n'avez-vous pas besoin de déclarer un constructeur par défaut si vous n'avez défini aucun autre constructeur?

Un constructeur est obligatoire pour les classes non statiques.
Donc, je pense que si vous n'avez défini aucun constructeur, le constructeur par défaut généré est juste une fonctionnalité pratique du compilateur C #, et votre classe ne serait pas valide sans constructeur. Donc rien de mal à générer implicitement un constructeur qui ne fait rien. Cela a certainement l'air plus propre que d'avoir des constructeurs vides tout autour.

Si vous avez déjà défini un constructeur, votre classe est valide, alors pourquoi le compilateur devrait-il supposer que vous voulez un constructeur par défaut? Et si vous n'en voulez pas? Implémenter un attribut pour dire au compilateur de ne pas générer ce constructeur par défaut? Je ne pense pas que ce serait une bonne idée.

Botz3000
la source
1

Le constructeur par défaut ne peut être construit que lorsque la classe n'a pas de constructeur. Les compilateurs sont écrits de manière à ne fournir cela que comme mécanisme de sauvegarde.

Si vous avez un constructeur paramétré, vous ne souhaitez peut-être pas qu'un objet soit créé à l'aide du constructeur par défaut. Si le compilateur avait fourni un constructeur par défaut, vous auriez dû écrire un constructeur sans argument et le rendre privé afin d'empêcher la création d'objets en utilisant aucun argument.

En outre, il y aurait plus de chances que vous oubliez de désactiver ou de «privatiser» le constructeur par défaut, et de provoquer ainsi une erreur fonctionnelle potentielle difficile à détecter.

Et maintenant, vous devez définir explicitement un constructeur sans argument si vous souhaitez qu'un objet soit créé de la manière par défaut ou en passant des paramètres. Ceci est fortement vérifié et le compilateur se plaint du contraire, assurant ainsi aucune faille ici.

cendres
la source
1

Prémisse

Ce comportement peut être vu comme une extension naturelle de la décision des classes d'avoir un constructeur sans paramètre public par défaut . Sur la base de la question posée, nous prenons cette décision comme une prémisse et supposons que nous ne la remettons pas en question dans ce cas.

Méthodes de suppression du constructeur par défaut

Il s'ensuit qu'il doit y avoir un moyen de supprimer le constructeur sans paramètre public par défaut. Cette suppression peut être effectuée des manières suivantes:

  1. Déclarer un constructeur sans paramètre non public
  2. Supprime automatiquement le constructeur sans paramètre lorsqu'un constructeur avec des paramètres est déclaré
  3. Un mot-clé / attribut à indiquer au compilateur pour supprimer le constructeur sans paramètre (assez gênant pour qu'il soit facile d'exclure)

Choisir la meilleure solution

Maintenant, nous nous demandons: s'il n'y a pas de constructeur sans paramètre, par quoi doit-il être remplacé? et Dans quels types de scénarios voudrions-nous supprimer le constructeur sans paramètre public par défaut?

Les choses commencent à se mettre en place. Premièrement, il doit être soit remplacé par un constructeur avec des paramètres, soit par un constructeur non public. Deuxièmement, les scénarios dans lesquels vous ne voulez pas d'un constructeur sans paramètre sont:

  1. Nous ne voulons pas du tout que la classe soit instanciée, ou nous voulons contrôler la visibilité du constructeur: déclarer un constructeur non public
  2. On veut forcer les paramètres à fournir lors de la construction: déclarer un constructeur avec des paramètres

Conclusion

Voilà, exactement les deux façons dont C #, C ++ et Java permettent la suppression du constructeur sans paramètre public par défaut.

Zaid Masud
la source
Clairement structuré et facile à suivre, +1. Mais en ce qui concerne (3.) ci-dessus: je ne pense pas qu'un mot-clé spécial pour la suppression du constructeur soit une idée aussi délicate, et en fait C ++ 11 l'a introduit = deleteà cette fin.
jogojapan
1

Je pense que cela est géré par le compilateur. Si vous ouvrez l' .netassembly dans, ILDASMvous verrez le constructeur par défaut, même s'il ne figure pas dans le code. Si vous définissez un constructeur paramétré, le constructeur par défaut ne sera pas vu.

En fait, lorsque vous définissez la classe (non statique), le compilateur fournit cette fonctionnalité en pensant que vous allez simplement créer une instance. Et si vous souhaitez exécuter une opération spécifique, vous aurez sûrement votre propre constructeur.

PQubeTechnologies
la source
0

C'est parce que lorsque vous ne définissez pas de constructeur, le compilateur génère automatiquement un constructeur pour vous qui ne prend aucun argument. Lorsque vous voulez quelque chose de plus de la part d'un constructeur, vous le remplacez. Ce n'est PAS une surcharge de fonction. Donc, le seul constructeur que le compilateur voit maintenant est votre constructeur qui prend un argument. Pour contrer ce problème, vous pouvez transmettre une valeur par défaut si le constructeur est passé sans valeur.

Vivek Pradhan
la source
0

Une classe a besoin d'un constructeur. C'est une exigence obligatoire.

  • Si vous n'en créez pas un, un constructeur sans paramètre vous sera fourni automatiquement.
  • Si vous ne voulez pas de constructeur sans paramètre, vous devez créer le vôtre.
  • Si vous avez besoin à la fois d'un constructeur sans paramètre et d'un constructeur basé sur un paramètre, vous pouvez les ajouter manuellement.

Je vais répondre à votre question par une autre, pourquoi voulons-nous toujours un constructeur sans paramètre par défaut? il y a des cas où cela n'est pas souhaité, de sorte que le développeur a le contrôle pour l'ajouter ou le supprimer selon ses besoins.

Jaider
la source