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;
}

J'ai attendu la sortie -5et 5, mais le résultat est le -5 et -5.

Je me demande pourquoi cette affaire va arriver?

At-il quelque chose à voir avec l'utilisation de std, ou quoi?

95
demandé sur Lii 2018-06-17 19:31:21

2 réponses

La spécification du langage permet aux implémentations d'implémenter <cmath> en déclarant (et en définissant) les fonctions standard dans global namespace, puis en les amenant dans l'espace de noms std au moyen de-declarations. Il n'est pas précisé si cette approche est utilisée

20.5.1.2 les en-Têtes
4 [...] Dans la bibliothèque standard c++, cependant, les déclarations (à l'exception des noms qui sont définis comme des macros en C) sont dans portée de l'espace de noms (6.3.6) de l'espace de noms std. Il n'est pas spécifié si ces noms (y compris les surcharges ajouté dans les Clauses 21 à 33 et L'Annexe D) sont d'abord déclarés dans la portée de l'espace de noms global et sont ensuite injectés dans l'espace de noms std par explicite using-declarations (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 que votre implémentation fournit ::abs, tandis que std::abs "fait simplement référence" à ::abs.

Une question qui reste dans ce cas est pourquoi en plus de la norme ::abs Vous avez pu déclarer votre propre ::abs, c'est-à-dire pourquoi il n'y a 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 comme appelées symboles faibles, vous permettant ainsi de les "remplacer" par vos propres définitions.

Ces deux facteurs créent ensemble l'effet que vous observez: ::abs entraîne également le remplacement de std::abs. La façon dont cela est en accord avec la norme linguistique est une autre histoire... Dans tous les cas, ne comptez pas sur ce comportement - il n'est pas garanti par la langue.

Dans GCC, ce comportement peut être reproduit par 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 pourrez également observer que la nouvelle définition de ::foo ("Goodbye!") dans le deuxième fichier source affecte également le comportement de N::foo. Les deux appels sortiront "Goodbye!". Et si vous supprimez la définition de ::foo du deuxième fichier source, les deux appels seront envoyés à la définition "originale" de ::foo et à la sortie "Hello!".


L'autorisation 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 C-style <math.h>, puis redéclare les fonctions dans std et ajouter des ajouts et des modifications spécifiques à C++. Si l'explication ci-dessus décrit correctement la mécanique interne de la question, alors une grande partie de celui-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" - il affichera -5 5. Cela se produit parce que la bibliothèque standard c n'a pas de 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 original réapparaîtra dans toute sa splendeur (sortie -5 -5).

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

90
répondu AnT 2018-06-18 14:10:58

Votre code provoque un comportement indéfini.

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

Chaque signature de fonction de la bibliothèque standard c déclarée avec liaison externe est réservée à l'implémentation pour une utilisation en tant que signature de fonction avec la 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 C Standard int abs(int);. Indépendamment de qui les en-têtes de vous en fait inclure ou si ces en-têtes mettent également des noms de bibliothèque C dans l'espace de noms global.

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

13
répondu M.M 2018-06-18 01:35:22