7
votes

Pourquoi ces deux constructeurs ne produisent pas une erreur d'ambiguïté?

Considérez les éléments suivants:

int main()
{
    return 0;
}


1 commentaires

S'il vous plaît étiquetez la question avec la langue que vous demandez.


5 Réponses :


7
votes

Il n'y a pas d'erreur de compilation car aucune erreur n'existe dans votre code. Tout comme définir deux fonctions: ils doivent avoir des signatures différentes, autres que cela, c'est possible. L'erreur de compilation de l'ambiguïté n'apparaît que lorsque vous essayez d'appeler l'une de ces fonctions. Même se produira en essayant d'appeler le constructeur par défaut de A , même s'il est privé: xxx

ceci compile, essayant d'appeler f () sans paramètres échoue, ce qui a du sens.

Essayez également: xxx

Si ceci doit donner une erreur? Non, car les deux f ont des signatures différentes. Dans ce cas, le compilateur peut résoudre l'ambiguïté, si vous appelez f sur un objet const , la méthode const sera appelée, et vice- Versa.


2 commentaires

Néanmoins, un avertissement serait formidable ici :) pourrait être difficile à généraliser cependant, et potentiellement coûteux (surtout que les définitions de classes sont analysées pour chaque TU Les classes sont incluses ...).


En fait, je reçois un avertissement dans vs2005 - "Constructeurs multiples par défaut" ... Vous pouvez bien sûr utiliser Pragmas pour l'avertissement spécifique pour les traiter comme des erreurs.



1
votes

Voici un exemple légèrement modifié que j'ai testé avec GCC sur Cygwin:

A a;


10 commentaires

Ce n'est pas le même code qu'il a affiché ... Bien sûr qu'il ne compile pas puisque vous appelez le constructeur par défaut de A.


J'utilise Eclipse, G ++ et le code compile super. Seulement lorsque j'essaie de créer une instance de "A" (I.e. int 'principale () {A A; Retour 0;}) Ensuite, le compilateur produit une erreur de compilation (ambiguïté).


Bien sûr, j'ai essayé de compiler avec un appel à une fonction. Si mon code ne compile pas et que le code d'origine compilait, la sémantique de C ++ est vraiment défectueuse. -5 pour C ++.


Mais il n'a aucun sens que le code d'origine compile non plus, car il définit un constructeur qui ne peut jamais être appelé. Cela n'a aucun sens.


Donc, par cette logique, avoir une fonction privée qui n'est jamais appelée interne ne devrait pas non plus compiler?


Mais vous pouvez toujours appeler la fonction privée à partir d'une autre fonction membre: il est toujours possible de l'appeler. Le constructeur par défaut ne peut en aucun cas être appelé, à moins que vous ne modifiez la signature de l'autre constructeur ou que vous le supprimez. Ce n'est pas la même logique.


@Giorgio - en fait, il est toujours possible d'appeler le constructeur par défaut ... bien qu'il nécessite un certain nombre de code suspect pour le faire. Normalement, vous souhaitez déclarer vos classes dans une en-tête afin que plusieurs unités de compilation puissent partager une définition unique; Toutefois, si vous définissez A dans une unité de compilation et omettez l'argument par défaut, ou omettez le constructeur avec l'argument par défaut entièrement, le code de cette unité de compilation peut appeler le constructeur par défaut. Cette duplication peut être réduite en utilisant le préprocesseur. Cependant, vous devriez éviter de créer un code potentiellement ambigieux dans la mesure du possible.


@MARC: Ce serait formidable si vous pouviez poster un exemple, c'est-à-dire une déclaration de classe et appelez au constructeur par défaut. Je suis curieux parce que je n'ai pas bien compris comment cela fonctionne.


@Giorgio - j'ai créé un Pastebin à démontrer. Dans ce cas, j'utilise le préprocesseur pour fournir une définition équivalente de ma classe au code qui souhaite appeler le constructeur par défaut. Rappelez-vous a (int x) et a (int x = 33) déclarer la même fonction, la seule différence est que si aucun argument n'est fourni, une fois que l'affaire provoquera une erreur. et l'autre fournira un argument par défaut. Encore une fois, je dirai que ce type de code devrait être considéré comme très suspect, mais c'est possible.


@Giorgio - j'ai décidé de développer également sur ma réponse, puisque la manière dont le C ++ traite de cela est assez pertinent pour la question, ainsi que les trivia amusantes. :)



0
votes

L'ambiguïté ne se pose pas car le constructeur privé n'est même pas pris en compte lorsque vous écrivez A a (1) , car vous y passez une argument et que le constructeur privé ne prend aucun argument. .

Cependant, si vous écrivez a a , il y aura une ambiguïté, car les deux constructeurs sont candidats et que le compilateur ne peut choisir lequel invoquer.


7 commentaires

Je pensais que le compilateur trouverait l'ambiguïté même lorsque l'utilisateur ne crée pas une instance. Je suppose que j'avais tort.


J'ai l'erreur même si je compile avec l'option -c: le compilateur le rejette déjà.


Alors qu'en pensez-vous, les gars? dépend du compilateur que l'utilisateur utilise?


@Ron_s: Cela ne dépend pas du compilateur. Tous les compilateurs produiront une erreur si vous écrivez a a , et tous les compilateurs ne produiraient pas d'erreur si vous écrivez a a (1) .


Bien sûr si j'écris "A A;" Ensuite, j'aurais une erreur de compilation.Je demandait si je ne créerai aucune instance de ..


@Ron_s: N'est-il pas évident de mon message et commencient que si vous n'écrivez pas a a , vous n'obtiriez aucune erreur?


Nawaz, oui c'est. Je pensais juste que le compilateur penserait à deux pas. Merci mon ami !



2
votes

déclarer des fonctions potentiellement ambiguës en C ++ ne produit aucune erreur d'ambiguïté. L'ambiguïté a lieu lorsque vous essayez de référez-vous à ces fonctions de manière ambiguë. Dans votre exemple, l'ambiguïté se produira si vous essayez de construire votre objet par défaut.

Strictement parlant, il est parfaitement normal d'avoir des déclarations dans un programme C ++ potentiellement ambigu (c'est-à-dire qui peut être mentionné de manière ambiguë). Par exemple, à première vue, ces fonctions surchargées ont l'air fine xxx

mais appelant foo (1u) déclenchera une erreur d'ambiguïté. Donc, une fois encore, l'ambiguïté est la propriété de la manière dont vous vous référez aux fonctions précédemment déclarées et non à la propriété des déclarations de fonction elles-mêmes.


11 commentaires

Quelle est la différence entre potentiellement ambigu et vraiment ambiguë? Je dirais que la déclaration d'origine est ambiguë. Point final.


@Giorgio: La déclaration "potentiellement ambiguë" est une déclaration qui peut être mentionnée ultérieurement de telle manière que cela déclenchera une erreur d'ambiguïté. "Vraiment ambigu" est une propriété de cette référence. Si cela déclenche l'erreur d'ambiguïté, il est "vraiment ambigu".


Et s'il ne peut exister aucune référence qui ne déclenche pas l'ambiguïté?


@Giorgio: comme? Si deux fonctions ont exactement le même nom et la même signature, vous violez probablement la règle d'une définition.


@Msalters: tel que l'exemple original: le constructeur par défaut A () ne peut jamais être invoqué car la syntaxe d'invocation ne peut pas être distinguée d'un (int x = 0) lorsque nous utilisons la valeur par défaut pour x. Apparemment, vous avez des signatures différentes A (), A (int x = 0) mais, en fait, a (int x = 0) définit implicitement deux fonctions: a (int x) et a () dans lequel x est juste une locale initialisée variable. La seconde de ces deux fonctions a la même signature que A (). C'est pourquoi vous allez toujours avoir une erreur de compilation lorsque vous essayez d'instancier à l'aide du constructeur A ().


@Giorgio: Dans ce cas, A (5) est toujours sans ambiguïté. C'est une "référence" qui ne déclenche pas l'ambiguïté. Votre question était spécifiquement sur les situations dans lesquelles toutes les expressions serait ambiguë.


Je voulais dire toutes les références au constructeur par défaut A (). Fondamentalement, vous pouvez déclarer un constructeur que vous ne pouvez pas invoquer: cela me semble assez inutile et je voudrais lui interdire.


@Giorgio: Eh bien, je comprends votre logique. Cependant, la langue ne nécessite pas que les compilateurs reconnaissent et diagnostiquent des situations relevant de votre ", il ne peut exister aucune référence qui ne déclenche pas l'ambiguïté". Je pense qu'il est intuitivement clair que la détection de telles situations est un problème de calcul assez difficile en général.


Si vous élargissez la signature A (int x = 0) sur la paire A () (int x = 0 est une variable locale) et A (int x), vous avez une solution assez facile.


@Andreyt: Je comprends également votre logique: il est plus facile de mettre en œuvre le compilateur traiter A () et A (int x = 0) comme des signatures différentes.


@Giorgio: Dans ce cas particulier, il est certainement facile. Mais en général, cela peut être très difficile.



2
votes

Votre code compile car il n'y a pas d'ambiguïté. Vous avez créé une classe avec deux constructeurs, qui prend toujours 0 arguments et celui qui prend toujours un argument, un int. Vous appelez alors sans ambiguïté le constructeur prenant une valeur INT. Que cela a une valeur par défaut n'a pas d'importance, il reste une signature complètement différente. Que les constructeurs sont potentiellement ambigus peu importe, le compilateur ne se plaint que lorsqu'un appel particulier est réellement ambigu.

Lorsque vous créez une instance de A sans arguments, il ne sait pas quel constructeur que vous souhaitez appeler: Constructeur par défaut ou le constructeur prenant un INT avec une valeur de paramètre de 0. Dans ce cas, il serait bien que C ++ remarque que le constructeur privé est inéligible, mais ce n'est pas toujours possible.

Ce comportement finit par être utile dans certaines circonstances (par exemple, si vous avez quelques surcharges impliquant des modèles, dont certaines se chevauchent si elles sont confiées aux bons types), cependant, pour des cas simples, je ferais simplement Le constructeur unique avec l'argument par défaut (de préférence marqué explicit à moins que vous ayez une raison vraiment vraiment bonne de la laisser implicite, puis je devrais deviner cette raison pour être sûr!)

- Edit -

amusons-les avec cela et essayons d'explorer davantage ce qui se passe. xxx

Notez que nous profitons du fait que le compilateur ne fait pas Ne sais pas sur les en-têtes; Au moment où il examine un fichier .CPP, le préprocesseur a déjà substitué #includes avec le corps de l'en-tête. Je joue d'être mon propre préprocesseur, faisant des choses dangereuses comme fournir plusieurs définitions différentes d'une classe. Plus tard, l'un des emplois de la liaison est de jeter tout sauf une de ces définitions. S'ils ne s'alignent pas de la bonne manière exacte, toutes sortes de mauvaises choses se produiront, comme vous le ferez dans la zone de crépuscule du comportement indéfini.

Notez que je faisais attention à fournir exactement la même mise en page pour ma classe dans chaque unité de compilation; Chaque définition a exactement 1 méthodes virtuelles int et 0. Notez que je n'ai pas introduit de méthodes supplémentaires (bien que cela puisse fonctionner; faire des choses comme celle-ci devrait être considérée comme une excellente suspicion), la seule chose qui a changé était des fonctions de membre non virtuelles (des constructeurs de puits), puis seulement pour Supprimez le constructeur par défaut. Changer et supprimer la valeur par défaut n'a changé rien de la définition de A :: A (int).

Je n'ai pas de copie de la spécification sur moi, je ne peux donc pas dire si mes changements soignés Tomber sous comportement indéfini ou comportement spécifique, mais je le traiterais comme tel pour le code de production et évitez de tirer profit de ces astuces.

et la réponse ultime à quel argument est utilisé à l'intérieur de Baz, .... 42!


5 commentaires

Du point de vue de la signature, il est absolument équivalent à invoquer un constructeur sans arguments ni constructeur avec un argument, mais en utilisant la valeur par défaut de l'argument. Donc, l'ambiguïté est déjà dans les signatures.


@Giorgio: La signature ne se soucie pas des arguments par défaut. Pensez-y en termes de pointeurs de fonction, le type du pointeur de fonction n'inclut que les types d'arguments réels, et non si l'un des arguments peut ou non être fait défaut.


@ Kerrek SB: "La signature ne se soucie pas des arguments par défaut." Je comprends cela, mais je pense que peut-être qu'il est faux de définir la signature de cette manière. Lorsque je définis un (int x = 0), je peux l'appeler avec un (), comme si la signature était une () et non a (int x). Il peut donc être utile de considérer un (int x = 0) ayant deux signatures: a (int x) et a (). De cette façon, vous pouvez détecter le choc entre les méthodes A () et A (int x = 0) de l'exemple.


@Giorgio - Je conviens que cela peut être utile d'y penser de cette façon, mais ce n'est pas la façon dont le compilateur y pense. Le compilateur voit un constructeur qui prend un INT et un constructeur qui ne prend aucun argument. Les arguments par défaut ne jouent pas du tout dans la fonction de surcharge / fonction de fonction, elles sont plutôt appliquées à l'heure d'appel.


@Giorgio - Cela peut aider à considérer cet exemple: permet de dire que vous avez trois fichiers .CPP et aucun fichier d'en-tête. Dans un cas, vous créez une fonction int ajouter5 (int x) {retour x + 5; } . Dans la seconde, vous déclarez Add5 comme: int 201 (int x = 10); et l'appelez sans arguments, imprimant les résultats. Dans la troisième, vous déclarez Add5 comme: int ajouter5 (int x = 20) et l'appelez sans arguments, imprimant les résultats. Ajouter un Main quelque part et appelez les deux fonctions qui appellent add5 . Que se passe-t-il lorsque vous compilez le programme? Si cela compile, quels numéros sont imprimés?