web-dev-qa-db-fra.com

Une déclaration peut-elle affecter l'espace de noms std?

#include <iostream>
#include <cmath>

/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
    return a > 0? -a : a;
}

int main() {
    int a = abs(-5);
    int b = std::abs(-5);
    std::cout<< a << std::endl << b << std::endl;
    return 0;
}

Je m'attendais à ce que la sortie soit -5et 5, mais le résultat est le -5 et -5.

Je me demande pourquoi cette affaire va arriver?

Cela a-t-il quelque chose à voir avec l'utilisation de std ou quoi?

94
Peter

La spécification de langage autorise les implémentations == implémente <cmath> En déclarant (et en définissant) les fonctions standard dans global et en les amenant ensuite dans cet espace de noms std à l'aide de user-declarations. Il n'est pas précisé si cette approche est utilisée

20.5.1.2 En-têtes
4 [...] Dans la bibliothèque standard C++, cependant, les déclarations (à l'exception des noms définis comme macros en C ) sont dans la portée de l’espace de nommage (6.3.6) de l’espace de nommage std. Il n'est pas précisé si ces noms (y compris les surcharges ajoutées aux clauses 21 à 33 et à l'annexe D) sont d'abord déclarés dans la portée de l'espace de noms global, puis injectés dans l'espace de noms std par des déclarations d'utilisation explicites (10.3.3). .

Apparemment, vous avez affaire à l’une des implémentations qui ont décidé de suivre cette approche (par exemple, GCC). C'est à dire. votre implémentation fournit ::abs, alors que std::abs se réfère simplement à ::abs.

Une question qui reste dans ce cas est pourquoi, en plus de la norme ::abs, Vous avez été en mesure de déclarer votre propre ::abs, C’est-à-dire pourquoi il n’ya pas d’erreur de définition multiple. Cela peut être dû à une autre fonctionnalité fournie par certaines implémentations (par exemple, GCC): elles déclarent les fonctions standard ainsi nommées symboles faibles, vous permettant ainsi de les "remplacer" par vos propres définitions.

Ces deux facteurs créent l’effet observé: le remplacement du symbole faible par ::abs Entraîne également le remplacement de std::abs. Le fait que cela soit en accord avec le langage standard diffère de la réalité ... Dans tous les cas, ne vous fiez pas à ce comportement - le langage ne le garantit pas.

Dans GCC, ce comportement peut être reproduit à l'aide de l'exemple minimaliste suivant. Un fichier source

#include <iostream>

void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }

Un autre fichier source

#include <iostream>

void foo();
namespace N { using ::foo; }

void foo() { std::cout << "Goodbye!" << std::endl; }

int main()
{
  foo();
  N::foo();
}

Dans ce cas, vous remarquerez également que la nouvelle définition de ::foo ("Goodbye!") Dans le deuxième fichier source affecte également le comportement de N::foo. Les deux appels produiront "Goodbye!". Et si vous supprimez la définition de ::foo Du deuxième fichier source, les deux appels seront acheminés vers la définition "originale" de ::foo Et en sortie "Hello!".


La permission donnée par le 20.5.1.2/4 ci-dessus est là pour simplifier la mise en œuvre de <cmath>. Les implémentations sont autorisées à inclure simplement le style C <math.h>, Puis à redéclarer les fonctions dans std et à ajouter des ajouts et des ajustements spécifiques à C++. Si l'explication ci-dessus décrit correctement la mécanique interne du problème, une grande partie de celle-ci dépend de la remplaçabilité des symboles faibles pour versions de style C des fonctions.

Notez que si nous remplaçons simplement globalement int par double dans le programme ci-dessus, le code (sous GCC) se comportera "comme prévu" - le résultat sera -5 5. Cela est dû au fait que la bibliothèque standard C n’a pas la fonction abs(double). En déclarant notre propre abs(double), nous ne remplaçons rien.

Mais si après le passage de int avec double, nous passons également de abs à fabs, le comportement étrange d'origine réapparaîtra dans toute sa splendeur (sortie -5 -5).

Ceci est cohérent avec l'explication ci-dessus.

89
AnT

Votre code provoque un comportement indéfini.

C++ 17 [noms.externes]/4:

Chaque signature de fonction de la bibliothèque standard C déclarée avec une liaison externe est réservée à l'implémentation pour une utilisation en tant que signature de fonction avec une liaison externe "C" et externe "C++", ou en tant que nom de la portée de l'espace de noms dans l'espace de noms global.

Vous ne pouvez donc pas créer une fonction avec le même prototype que la fonction de bibliothèque Standard C int abs(int);. Quels que soient les en-têtes que vous incluez réellement ou si ces en-têtes insèrent également des noms de bibliothèque C dans l'espace de nom global.

Cependant, il serait permis de surcharger abs si vous fournissiez différents types de paramètres.

13
M.M