Espaces de noms sans nom / anonymes vs fonctions statiques


508

Une caractéristique de C ++ est la possibilité de créer des espaces de noms sans nom (anonymes), comme ceci:

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace

On pourrait penser qu'une telle fonctionnalité serait inutile - puisque vous ne pouvez pas spécifier le nom de l'espace de noms, il est impossible d'accéder à quoi que ce soit à l'intérieur de l'extérieur. Mais ces espaces de noms sans nom sont accessibles dans le fichier dans lequel ils sont créés, comme si vous y aviez une clause using implicite.

Ma question est: pourquoi ou quand est-ce préférable à l'utilisation de fonctions statiques? Ou s'agit-il essentiellement de deux façons de faire exactement la même chose?


13
En C ++ 11, l'utilisation de staticdans ce contexte n'était pas obsolète ; bien que l' espace de noms sans nom soit une alternative supérieure àstatic , il y a des cas où il échoue quand staticvient à la rescousse .
legends2k

Réponses:


332

La norme C ++ lit à la section 7.3.1.1 Espaces de noms sans nom, paragraphe 2:

L'utilisation du mot clé statique est déconseillée lors de la déclaration d'objets dans une étendue d'espace de noms, l'espace de noms sans nom fournit une alternative supérieure.

Statique s'applique uniquement aux noms d'objets, de fonctions et d'unions anonymes, pas aux déclarations de type.

Éditer:

La décision de déprécier cette utilisation du mot-clé statique (affecter la visibilité d'une déclaration de variable dans une unité de traduction) a été inversée ( ref ). Dans ce cas, l'utilisation d'un espace de noms statique ou sans nom redevient essentiellement deux façons de faire exactement la même chose. Pour plus de discussion, veuillez consulter cette question SO.

Les espaces de noms sans nom ont toujours l'avantage de vous permettre de définir des types d'unités de traduction locales. Veuillez consulter cette question SO pour plus de détails.

Nous remercions Mike Percy d' avoir porté cela à mon attention.


39
Head Geek pose des questions sur les mots clés statiques utilisés uniquement pour les fonctions. Le mot clé statique appliqué à l'entité déclarée dans la portée de l'espace de noms spécifie sa liaison interne. L'entité déclarée dans un espace de noms anonyme a une liaison externe (C ++ / 3.5), mais elle est garantie de vivre dans une étendue de nom unique. Cet anonymat de l'espace de noms sans nom cache effectivement sa déclaration, le rendant accessible uniquement à partir d'une unité de traduction. Ce dernier fonctionne effectivement de la même manière que le mot-clé statique.
mloskot

5
quel est l'inconvénient de la liaison externe? Cela pourrait-il affecter l'inline?
Alex

17
Ceux du comité de conception C ++ qui ont déclaré que le mot clé statique est obsolète n'ont probablement jamais travaillé avec un énorme code C dans un grand système réel ... (Vous voyez immédiatement un mot clé statique mais pas l'espace de noms anonyme s'il contient beaucoup de déclarations avec de gros commentaires blocs.)
Calmarius

23
Étant donné que cette réponse apparaît sur Google en tant que résultat supérieur pour "l'espace de noms anonyme c ++", il convient de noter que l'utilisation de l'électricité statique n'est plus déconseillée. Voir stackoverflow.com/questions/4726570/… et open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 pour plus d'informations.
Michael Percy

2
@ErikAronesty Cela semble faux. Avez-vous un exemple reproductible? Depuis C ++ 11 - et même avant dans certains compilateurs - les namespaces sans nom ont implicitement un lien interne, donc il ne devrait pas y avoir de différence. Tous les problèmes qui pouvaient survenir auparavant à cause d'une mauvaise formulation ont été résolus en faisant de cette exigence en C ++ 11.
underscore_d

73

Le fait de placer des méthodes dans un espace de noms anonyme vous empêche de violer accidentellement la règle de définition unique , ce qui vous permet de ne jamais vous soucier de nommer vos méthodes d'assistance de la même manière qu'une autre méthode à laquelle vous pouvez vous connecter.

Et, comme l'a souligné luke, les espaces de noms anonymes sont préférés par la norme aux membres statiques.


2
Je faisais référence aux fonctions autonomes statiques (c'est-à-dire les fonctions de portée de fichier), et non aux fonctions membres statiques. Les fonctions autonomes statiques sont sensiblement les mêmes que les fonctions dans un espace de noms sans nom, d'où la question.
Head Geek

2
Ah; eh bien, l'ODR s'applique toujours. Modifié pour supprimer le paragraphe.
hazzen

que je reçois, ODR pour une fonction statique ne fonctionne pas quand elle est définie dans l'en-tête et cet en-tête est inclus dans plus d'une unité de traduction, non? dans ce cas, vous recevez plusieurs copies de la même fonction
Andriy Tylychko

@Andy T: Vous ne voyez pas vraiment les "définitions multiples" en cas d'en-tête inclus. Le préprocesseur s'en charge. À moins qu'il ne soit nécessaire d'étudier la sortie générée par le préprocesseur, qui me semble plutôt exotique et rare. Il existe également une bonne pratique pour inclure des "gardes" dans les fichiers d'en-tête, comme: "#ifndef SOME_GUARD - #define SOME_GUARD ..." qui est censé empêcher le préprocesseur d'inclure deux fois le même en-tête.
Nikita Vorontsov

@NikitaVorontsov, le gardien peut empêcher d'inclure le même en-tête dans la même unité de traduction, mais il autorise plusieurs définitions dans différentes unités de traduction. Cela peut provoquer une erreur de l'éditeur de liens "définitions multiples" sur toute la ligne.
Alex

37

Il y a un cas de bord où l'électricité statique a un effet surprenant (du moins c'était pour moi). La norme C ++ 03 stipule dans 14.6.4.2/1:

Pour un appel de fonction qui dépend d'un paramètre de modèle, si le nom de la fonction est un identifiant non qualifié mais pas un identifiant de modèle , les fonctions candidates sont trouvées en utilisant les règles de recherche habituelles (3.4.1, 3.4.2) sauf que:

  • Pour la partie de la recherche utilisant la recherche de nom non qualifié (3.4.1), seules les déclarations de fonction avec lien externe à partir du contexte de définition de modèle sont trouvées.
  • Pour la partie de la recherche utilisant des espaces de noms associés (3.4.2), seules les déclarations de fonction avec liaison externe trouvées dans le contexte de définition de modèle ou le contexte d'instanciation de modèle sont trouvées.

...

Le code ci-dessous appellera foo(void*)et pas foo(S const &)comme vous pourriez vous y attendre.

template <typename T>
int b1 (T const & t)
{
  foo(t);
}

namespace NS
{
  namespace
  {
    struct S
    {
    public:
      operator void * () const;
    };

    void foo (void*);
    static void foo (S const &);   // Not considered 14.6.4.2(b1)
  }

}

void b2()
{
  NS::S s;
  b1 (s);
}

En soi, ce n'est probablement pas si grave, mais cela met en évidence que pour un compilateur C ++ entièrement conforme (c'est-à-dire avec un support pour export), le staticmot - clé aura toujours des fonctionnalités qui ne sont pas disponibles autrement.

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
  foo(t);
}

// foo.cc
#include "bar.h"
namespace NS
{
  namespace
  {
    struct S
    {
    };

    void foo (S const & s);  // Will be found by different TU 'bar.cc'
  }
}

void b2()
{
  NS::S s;
  b1 (s);
}

La seule façon de s'assurer que la fonction dans notre espace de noms sans nom ne sera pas trouvée dans les modèles utilisant ADL est de la créer static.

Mise à jour pour C ++ moderne

Depuis C ++ '11, les membres d'un espace de noms sans nom ont implicitement une liaison interne (3.5 / 4):

Un espace de noms sans nom ou un espace de noms déclaré directement ou indirectement dans un espace de noms sans nom a une liaison interne.

Mais en même temps, 14.6.4.2/1 a été mis à jour pour supprimer la mention de liaison (cela provient de C ++ '14):

Pour un appel de fonction où l'expression postfixe est un nom dépendant, les fonctions candidates sont trouvées en utilisant les règles de recherche habituelles (3.4.1, 3.4.2) sauf que:

  • Pour la partie de la recherche utilisant la recherche de nom non qualifié (3.4.1), seules les déclarations de fonction du contexte de définition de modèle sont trouvées.

  • Pour la partie de la recherche utilisant des espaces de noms associés (3.4.2), seules les déclarations de fonction trouvées dans le contexte de définition de modèle ou le contexte d'instanciation de modèle sont trouvées.

Le résultat est que cette différence particulière entre les membres d'espace de noms statiques et non nommés n'existe plus.


3
Le mot-clé d'exportation n'est-il pas censé être mort à froid? Les seuls compilateurs prenant en charge "l'exportation" sont expérimentaux, et à moins de surprises, "l'exportation" ne sera même pas implémentée dans d'autres en raison d'effets secondaires inattendus (en plus de ne pas le faire, c'était prévu)
paercebal

2
Voir l'article de Herb Sutter sur le sujet: gotw.ca/publications/mill23-x.htm
paercebal

3
Le front-end d'Edison Design Group (EDG) est tout sauf expérimental. Il s'agit presque certainement de l'implémentation C ++ la plus conforme au monde. Le compilateur Intel C ++ utilise EDG.
Richard Corden,

1
Quelle fonctionnalité C ++ n'a pas «d'effets secondaires inattendus»? Dans le cas de l'exportation, c'est qu'une fonction d'espace de noms sans nom sera trouvée à partir d'un autre TU - c'est la même chose que si vous incluiez directement la définition du modèle. Ce serait plus surprenant si ce n'était pas comme ça!
Richard Corden

Je pense que vous avez une faute de frappe là-bas - pour NS::Stravailler, n'a pas Sbesoin de ne pas être à l'intérieur namespace {}?
Eric

12

J'ai récemment commencé à remplacer des mots clés statiques par des espaces de noms anonymes dans mon code, mais j'ai immédiatement rencontré un problème où les variables de l'espace de noms n'étaient plus disponibles pour inspection dans mon débogueur. J'utilisais VC60, donc je ne sais pas si ce n'est pas un problème avec d'autres débogueurs. Ma solution de contournement consistait à définir un espace de noms «module», où je lui ai donné le nom de mon fichier cpp.

Par exemple, dans mon fichier XmlUtil.cpp, je définis un espace XmlUtil_I { ... }de noms pour toutes mes variables et fonctions de module. De cette façon, je peux appliquer la XmlUtil_I::qualification dans le débogueur pour accéder aux variables. Dans ce cas, le le _Idistingue d'un espace de noms public tel que XmlUtilcelui que je souhaite utiliser ailleurs.

Je suppose qu'un inconvénient potentiel de cette approche par rapport à une approche vraiment anonyme est que quelqu'un pourrait violer la portée statique souhaitée en utilisant le qualificateur d'espace de noms dans d'autres modules. Je ne sais pas si c'est une préoccupation majeure.


7
Je l'ai fait aussi, mais avec #if DEBUG namespace BlahBlah_private { #else namespace { #endif, donc "l'espace de noms du module" n'est présent que dans les versions de débogage et le véritable espace de noms anonyme est utilisé autrement. Ce serait bien si les débogueurs donnaient une bonne façon de gérer cela. L'oxygène se confond également avec lui.
Kristopher Johnson

4
l'espace de noms sans nom n'est pas vraiment un remplacement viable pour statique. statique signifie "vraiment cela n'est jamais lié en dehors du TU". un espace de noms sans nom signifie "il est toujours exporté, comme un nom aléatoire, au cas où il serait appelé à partir d'une classe parent qui est en dehors de la TU" ...
Erik Aronesty

7

L'utilisation d'un mot clé statique à cet effet est déconseillée par la norme C ++ 98. Le problème avec statique est qu'il ne s'applique pas à la définition de type. C'est également un mot clé surchargé utilisé de différentes manières dans différents contextes, donc les espaces de noms sans nom simplifient un peu les choses.


1
Si vous souhaitez utiliser un type uniquement dans une seule unité de traduction, déclarez-le dans le fichier .cpp. De toute façon, il ne sera pas accessible à partir d'autres unités de traduction.
Calmarius

4
Vous pensez, non? Mais si une autre unité de traduction (= fichier cpp) dans la même application déclare un type avec le même nom, vous rencontrez des problèmes plutôt difficiles à déboguer :-). Par exemple, vous pouvez vous retrouver avec des situations où la vtable pour l'un des types est utilisée lors de l'appel de méthodes sur l'autre.
avl_sweden

1
Plus obsolète. Et les définitions de type ne sont pas exportées, donc cela n'a aucun sens. la statique est utile pour les fonctions autonomes et les variables globales. les espaces de noms sans nom sont utiles pour les classes.
Erik Aronesty

6

Par expérience, je noterai que même s'il s'agit du moyen C ++ de placer des fonctions anciennement statiques dans l'espace de noms anonyme, les anciens compilateurs peuvent parfois avoir des problèmes avec cela. Je travaille actuellement avec quelques compilateurs pour nos plates-formes cibles, et le compilateur Linux plus moderne est très bien avec le placement de fonctions dans l'espace de noms anonyme.

Mais un ancien compilateur fonctionnant sur Solaris, auquel nous sommes mariés jusqu'à une version future non spécifiée, l'acceptera parfois, et d'autres fois le signalera comme une erreur. L'erreur n'est pas ce qui m'inquiète, c'est ce qu'elle pourrait faire lorsqu'elle l' accepte . Donc, jusqu'à ce que nous passions à la modernité, nous utilisons toujours des fonctions statiques (généralement à portée de classe) où nous préférerions l'espace de noms anonyme.


3

De plus si l'on utilise un mot-clé statique sur une variable comme cet exemple:

namespace {
   static int flag;
}

Il ne serait pas visible dans le fichier de mappage


7
Ensuite, vous n'avez pas du tout besoin d'espace de noms anonyme.
Calmarius

2

Une différence spécifique au compilateur entre les espaces de noms anonymes et les fonctions statiques peut être observée lors de la compilation du code suivant.

#include <iostream>

namespace
{
    void unreferenced()
    {
        std::cout << "Unreferenced";
    }

    void referenced()
    {
        std::cout << "Referenced";
    }
}

static void static_unreferenced()
{
    std::cout << "Unreferenced";
}

static void static_referenced()
{
    std::cout << "Referenced";
}

int main()
{
    referenced();
    static_referenced();
    return 0;
}

La compilation de ce code avec VS 2017 (en spécifiant l'indicateur d'avertissement de niveau 4 / W4 pour activer l' avertissement C4505: la fonction locale non référencée a été supprimée ) et gcc 4.9 avec l'indicateur -Wunused-function ou -Wall indique que VS 2017 ne produira qu'un avertissement pour la fonction statique inutilisée. gcc 4.9 et supérieur, ainsi que clang 3.3 et supérieur, produiront des avertissements pour la fonction non référencée dans l'espace de noms et également un avertissement pour la fonction statique inutilisée.

Démo en direct de gcc 4.9 et MSVC 2017


2

Personnellement, je préfère les fonctions statiques aux espaces de noms sans nom pour les raisons suivantes:

  • Il est évident et clair à partir de la seule définition de fonction qu'elle est privée à l'unité de traduction où elle est compilée. Avec un espace de noms sans nom, vous devrez peut-être faire défiler et rechercher pour voir si une fonction se trouve dans un espace de noms.

  • Les fonctions dans les espaces de noms peuvent être traitées comme externes par certains (plus anciens) compilateurs. Dans VS2017, ils sont toujours externes. Pour cette raison, même si une fonction se trouve dans un espace de noms sans nom, vous pouvez toujours vouloir les marquer comme statiques.

  • Les fonctions statiques se comportent de manière très similaire en C ou C ++, tandis que les espaces de noms sans nom sont évidemment C ++ uniquement. les espaces de noms sans nom ajoutent également un niveau supplémentaire dans l'indentation et je n'aime pas ça :)

Donc, je suis heureux de voir que l'utilisation de static pour les fonctions n'est plus obsolète .


Les fonctions dans les espaces de noms anonymes sont censées avoir une liaison externe. Ils sont juste mutilés pour les rendre uniques. Seul le staticmot - clé applique réellement le lien local à une fonction. De plus, seul un fou délirant ajouterait certainement une indentation pour les espaces de noms?
Roflcopter4

0

Ayant appris cette fonctionnalité tout à l'heure en lisant votre question, je ne peux que spéculer. Cela semble offrir plusieurs avantages par rapport à une variable statique au niveau du fichier:

  • Les espaces de noms anonymes peuvent être imbriqués les uns dans les autres, offrant plusieurs niveaux de protection auxquels les symboles ne peuvent pas échapper.
  • Plusieurs espaces de noms anonymes peuvent être placés dans le même fichier source, créant en effet différentes étendues de niveau statique dans le même fichier.

Je serais intéressé à savoir si quelqu'un a utilisé des espaces de noms anonymes dans du vrai code.


4
Bonnes spéculations, mais fausses. La portée de ces espaces de noms est à l'échelle du fichier.
Konrad Rudolph

Ce n'est pas tout à fait vrai, si vous définissez un espace de noms anonyme dans un autre espace de noms, il n'est toujours qu'à l'échelle du fichier et ne peut être vu que comme étant dans cet espace de noms. Essayez-le.
Greg Rogers

Je peux me tromper mais, je suppose que non, ce n'est pas à l'échelle du fichier: il n'est accessible qu'au code après l'espace de noms anonyme. C'est une chose subtile, et généralement, je ne voudrais pas polluer une source avec plusieurs espaces de noms anonymes ... Pourtant, cela peut avoir des utilisations.
paercebal

0

La différence est le nom de l'identifiant modifié ( _ZN12_GLOBAL__N_11bEvs _ZL1b, ce qui n'a pas vraiment d'importance, mais les deux sont assemblés en symboles locaux dans la table des symboles (absence de .globaldirective asm).

#include<iostream>
namespace {
   int a = 3;
}

static int b = 4;
int c = 5;

int main (){
    std::cout << a << b << c;
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4
_ZL1b:
        .long   4
        .globl  c
        .align 4
        .type   c, @object
        .size   c, 4
c:
        .long   5
        .text

Quant à un espace de noms anonyme imbriqué:

namespace {
   namespace {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

Tous les espaces de noms anonymes de 1er niveau dans l'unité de traduction sont combinés les uns avec les autres, Tous les espaces de noms anonymes imbriqués de 2e niveau dans l'unité de traduction sont combinés les uns avec les autres

Vous pouvez également avoir un espace de noms imbriqué (en ligne) dans un espace de noms anonyme

namespace {
   namespace A {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11A1aE, @object
        .size   _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

which for the record demangles as:
        .data
        .align 4
        .type   (anonymous namespace)::A::a, @object
        .size   (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
        .long   3
        .align 4
        .type   b, @object
        .size   b, 4

Vous pouvez également avoir des espaces de noms en ligne anonymes, mais pour autant que je sache, inlinesur un espace de noms anonyme a un effet 0

inline namespace {
   inline namespace {
       int a = 3;
    }
}

_ZL1b: _Zsignifie qu'il s'agit d'un identifiant mutilé. Lsignifie que c'est un symbole local à travers static. 1est la longueur de l'identifiant b, puis l'identifiantb

_ZN12_GLOBAL__N_11aE _Zsignifie qu'il s'agit d'un identifiant mutilé. Nsignifie qu'il s'agit d'un espace de noms 12est la longueur du nom d'espace de noms anonyme _GLOBAL__N_1, puis le nom de l'espace de noms anonyme _GLOBAL__N_1, puis 1la longueur de l'identifiant a, aest l'identifiant aet Eferme l'identifiant qui réside dans un espace de noms.

_ZN12_GLOBAL__N_11A1aE est le même que ci-dessus, sauf qu'il y a un autre niveau d'espace de noms dedans 1A

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.