Espacios de nombres anónimos / sin nombre versus funciones estáticas

508

Una característica de C ++ es la capacidad de crear espacios de nombres sin nombre (anónimos), de esta manera:

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

Pensaría que tal característica sería inútil, ya que no puede especificar el nombre del espacio de nombres, es imposible acceder a nada desde fuera. Pero estos espacios de nombres sin nombre son accesibles dentro del archivo en el que se crean, como si tuviera una cláusula de uso implícita.

Mi pregunta es, ¿por qué o cuándo sería preferible usar funciones estáticas? ¿O son esencialmente dos formas de hacer exactamente lo mismo?

Head Geek
fuente
13
En C ++ 11, el uso de staticen este contexto no estaba en desuso ; Aunque el espacio de nombres sin nombre es una alternativa superior astatic , hay casos en los que falla cuando se statictrata del rescate .
legends2k

Respuestas:

332

El estándar C ++ se lee en la sección 7.3.1.1 Espacios de nombres sin nombre, párrafo 2:

El uso de la palabra clave estática está en desuso cuando se declaran objetos en un ámbito de espacio de nombres, el espacio de nombres sin nombre proporciona una alternativa superior.

Estático solo se aplica a nombres de objetos, funciones y uniones anónimas, no a declaraciones de tipo.

Editar:

La decisión de desaprobar este uso de la palabra clave estática (afectar la visibilidad de una declaración de variable en una unidad de traducción) se ha revertido ( ref ). En este caso, el uso de un espacio de nombres estático o sin nombre vuelve a ser esencialmente dos formas de hacer exactamente lo mismo. Para más discusión por favor vea esta pregunta SO.

Los espacios de nombres sin nombre aún tienen la ventaja de permitirle definir tipos locales de unidades de traducción. Consulte esta pregunta SO para obtener más detalles.

El crédito es para Mike Percy por llamar mi atención sobre esto.

luke
fuente
39
Head Geek pregunta acerca de la palabra clave estática utilizada solo para funciones. La palabra clave estática aplicada a la entidad declarada en el ámbito del espacio de nombres especifica su enlace interno. La entidad declarada en el espacio de nombres anónimo tiene un enlace externo (C ++ / 3.5), sin embargo, se garantiza que viva en un ámbito con un nombre único. Este anonimato del espacio de nombres sin nombre oculta efectivamente su declaración, haciéndolo accesible solo desde una unidad de traducción. Este último funciona efectivamente de la misma manera que la palabra clave estática.
mloskot
55
¿Cuál es el inconveniente de la vinculación externa? ¿Podría esto afectar la alineación?
Alex
17
Aquellos en el comité de diseño de C ++ que dijeron que la palabra clave estática está en desuso probablemente nunca trabajaron con un código C enorme en un gran sistema del mundo real ... (Inmediatamente ves una palabra clave estática pero no el espacio de nombres anónimo si contiene muchas declaraciones con comentarios grandes bloques.)
Calmarius
23
Debido a que esta respuesta aparece en Google como un resultado superior para el "espacio de nombres anónimo c ++", debe tenerse en cuenta que el uso de static ya no está en desuso. Consulte stackoverflow.com/questions/4726570/… y open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 para obtener más información.
Michael Percy
2
@ ErikAronesty Eso suena mal. ¿Tienes un ejemplo reproducible? A partir de C ++ 11, e incluso antes de eso en algunos compiladores, los namespaces sin nombre tienen implícitamente un enlace interno, por lo que no debería haber ninguna diferencia. Cualquier problema que pudiera haber surgido anteriormente debido a una redacción deficiente se resolvió al hacer esto un requisito en C ++ 11.
underscore_d
73

Poner los métodos en un espacio de nombres anónimo le impide violar accidentalmente la Regla de una definición , lo que le permite no preocuparse por nombrar sus métodos auxiliares de la misma manera que con cualquier otro método que pueda vincular.

Y, como señaló Luke, los espacios de nombres anónimos son preferidos por el estándar sobre los miembros estáticos.

hazzen
fuente
2
Me refería a funciones autónomas estáticas (es decir, funciones de ámbito de archivo), no a funciones miembro estáticas. Las funciones independientes estáticas son muy similares a las funciones en un espacio de nombres sin nombre, de ahí la pregunta.
Head Geek
2
Ah bueno, la ODR todavía se aplica. Editado para eliminar el párrafo.
Hazzen
a medida que obtengo, ODR para una función estática no funciona cuando se define en el encabezado y este encabezado se incluye en más de una unidad de traducción, ¿verdad? en este caso, recibe múltiples copias de la misma función
Andriy Tylychko
@ Andy T: Realmente no ves las "definiciones múltiples" en caso de encabezado incluido. El preprocesador se encarga de ello. A menos que sea necesario estudiar el resultado que ha generado el preprocesador, que para mí parece bastante exótico y raro. También es una buena práctica incluir "guardias" en los archivos de encabezado, como: "#ifndef SOME_GUARD - #define SOME_GUARD ...", que se supone que evita que el preprocesador incluya el mismo encabezado dos veces.
Nikita Vorontsov
@NikitaVorontsov el guardia puede evitar incluir el mismo encabezado en la misma unidad de traducción, sin embargo, permite múltiples definiciones en diferentes unidades de traducción. Esto podría causar un error en el enlazador "definiciones múltiples" en el futuro.
Alex
37

Hay un caso extremo en el que la estática tiene un efecto sorprendente (al menos para mí). El estándar C ++ 03 establece en 14.6.4.2/1:

Para una llamada de función que depende de un parámetro de plantilla, si el nombre de la función es una identificación no calificada pero no una identificación de plantilla , las funciones candidatas se encuentran utilizando las reglas de búsqueda habituales (3.4.1, 3.4.2) excepto que:

  • Para la parte de la búsqueda que utiliza la búsqueda de nombre no calificado (3.4.1), solo se encuentran las declaraciones de función con enlace externo desde el contexto de definición de plantilla.
  • Para la parte de la búsqueda que utiliza espacios de nombres asociados (3.4.2), solo se encuentran las declaraciones de función con enlace externo que se encuentran en el contexto de definición de plantilla o en el contexto de instanciación de plantilla.

...

El código a continuación llamará foo(void*)y no foo(S const &)como es de esperar.

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 sí mismo, esto probablemente no sea un gran problema, pero destaca que para un compilador de C ++ totalmente compatible (es decir, uno con soporte para export) la staticpalabra clave seguirá teniendo una funcionalidad que no está disponible de ninguna otra manera.

// 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 única forma de garantizar que la función en nuestro espacio de nombres sin nombre no se encuentre en las plantillas que usan ADL es hacerlo static.

Actualización para Modern C ++

A partir de C ++ '11, los miembros de un espacio de nombres sin nombre tienen vinculación interna implícitamente (3.5 / 4):

Un espacio de nombres sin nombre o un espacio de nombres declarado directa o indirectamente dentro de un espacio de nombres sin nombre tiene un enlace interno.

Pero al mismo tiempo, se actualizó 14.6.4.2/1 para eliminar la mención de vinculación (tomado de C ++ '14):

Para una llamada de función donde la expresión postfix es un nombre dependiente, las funciones candidatas se encuentran utilizando las reglas de búsqueda habituales (3.4.1, 3.4.2), excepto que:

  • Para la parte de la búsqueda que utiliza la búsqueda de nombre no calificado (3.4.1), solo se encuentran las declaraciones de función del contexto de definición de plantilla.

  • Para la parte de la búsqueda que utiliza espacios de nombres asociados (3.4.2), solo se encuentran las declaraciones de función que se encuentran en el contexto de definición de plantilla o en el contexto de instanciación de plantilla.

El resultado es que esta diferencia particular entre miembros de espacio de nombres estáticos y sin nombre ya no existe.

Richard Corden
fuente
3
¿No se supone que la palabra clave de exportación está muerta? Los únicos compiladores que admiten "exportación" son los experimentales y, a menos que las sorpresas, la "exportación" ni siquiera se implemente en otros debido a efectos secundarios inesperados (además de no hacerlo para lo que se esperaba)
paercebal
2
Vea el artículo de Herb Sutter sobre el tema: gotw.ca/publications/mill23-x.htm
paercebal
3
El front-end de Edison Design Group (EDG) es todo menos experimental. Es casi seguro que es la implementación de C ++ más estándar del mundo. El compilador Intel C ++ usa EDG.
Richard Corden
1
¿Qué característica de C ++ no tiene 'efectos secundarios inesperados'? En el caso de la exportación, es que se encontrará una función de espacio de nombres sin nombre desde una TU diferente, que es lo mismo que si incluyera directamente la definición de plantilla. ¡Sería más sorprendente si no fuera así!
Richard Corden
Creo que tiene un error tipográfico allí, para NS::Strabajar, ¿no Snecesita no estar adentro namespace {}?
Eric
12

Recientemente comencé a reemplazar palabras clave estáticas con espacios de nombres anónimos en mi código, pero inmediatamente me encontré con un problema en el que las variables en el espacio de nombres ya no estaban disponibles para su inspección en mi depurador. Estaba usando VC60, así que no sé si eso no es un problema con otros depuradores. Mi solución fue definir un espacio de nombres 'módulo', donde le di el nombre de mi archivo cpp.

Por ejemplo, en mi archivo XmlUtil.cpp, defino un espacio XmlUtil_I { ... }de nombres para todas las variables y funciones de mi módulo. De esa manera puedo aplicar la XmlUtil_I::calificación en el depurador para acceder a las variables. En este caso, lo _Idistingue de un espacio de nombres público como el XmlUtilque tal vez quiera usar en otro lugar.

Supongo que una desventaja potencial de este enfoque en comparación con uno verdaderamente anónimo es que alguien podría violar el alcance estático deseado al usar el calificador de espacio de nombres en otros módulos. Sin embargo, no sé si eso es una preocupación importante.

Evg
fuente
77
También he hecho esto, pero con #if DEBUG namespace BlahBlah_private { #else namespace { #endif, por lo que el "espacio de nombres del módulo" solo está presente en las compilaciones de depuración y el espacio de nombres anónimo verdadero se usa de lo contrario. Sería bueno si los depuradores dieran una buena manera de manejar esto. Doxygen también se confunde.
Kristopher Johnson
44
el espacio de nombres sin nombre no es realmente un reemplazo viable para la estática. estático significa "realmente esto nunca se vincula fuera de la TU". espacio de nombres sin nombre significa "todavía se exporta, como un nombre aleatorio, en caso de que se llame desde una clase principal que está fuera de la TU" ...
Erik Aronesty
7

El uso de la palabra clave estática para ese fin está en desuso por el estándar C ++ 98. El problema con la estática es que no se aplica a la definición de tipo. También es una palabra clave sobrecargada utilizada de diferentes maneras en diferentes contextos, por lo que los espacios de nombres sin nombre simplifican un poco las cosas.

Firas Assaad
fuente
1
Si desea utilizar un tipo solo en una única unidad de traducción, declare dentro del archivo .cpp. No será accesible desde otras unidades de traducción de todos modos.
Calmarius
44
Pensarías, ¿no? Pero si otra unidad de traducción (= archivo cpp) en la misma aplicación alguna vez declara un tipo con el mismo nombre, se encontrará con problemas bastante difíciles de depurar :-). Por ejemplo, puede terminar con situaciones en las que se usa la tabla vtable para uno de los tipos al llamar a métodos en el otro.
avl_sweden
1
Ya no está en desuso. Y las defs de tipo no se exportan, por lo que no tiene sentido. las estadísticas son útiles para funciones independientes y variables globales. los espacios de nombres sin nombre son útiles para las clases.
Erik Aronesty
6

Por experiencia, solo notaré que si bien es la forma en C ++ de poner funciones anteriormente estáticas en el espacio de nombres anónimo, los compiladores más antiguos a veces pueden tener problemas con esto. Actualmente trabajo con algunos compiladores para nuestras plataformas de destino, y el compilador de Linux más moderno está bien para colocar funciones en el espacio de nombres anónimo.

Pero un compilador más antiguo que se ejecuta en Solaris, con el que estamos casados ​​hasta una versión futura no especificada, a veces lo aceptará, y otras veces lo marcará como un error. El error no es lo que me preocupa, es lo que podría estar haciendo cuando lo acepta . Entonces, hasta que seamos modernos en todos los ámbitos, todavía estamos usando funciones estáticas (generalmente de ámbito de clase) donde preferiríamos el espacio de nombres anónimo.

Don Wakefield
fuente
3

Además, si se usa una palabra clave estática en una variable como este ejemplo:

namespace {
   static int flag;
}

No se vería en el archivo de mapeo

Chris
fuente
77
Entonces no necesitas ningún espacio de nombres anónimo.
Calmarius
2

Se puede ver una diferencia específica del compilador entre espacios de nombres anónimos y funciones estáticas compilando el siguiente código.

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

Compilar este código con VS 2017 (especificando el indicador de advertencia de nivel 4 / W4 para habilitar el aviso C4505: se ha eliminado la función local sin referencia ) y gcc 4.9 con la función -Wunused-function o -Wall indica que VS 2017 solo generará una advertencia para La función estática no utilizada. gcc 4.9 y superior, así como clang 3.3 y superior, generarán advertencias para la función no referenciada en el espacio de nombres y también una advertencia para la función estática no utilizada.

Demostración en vivo de gcc 4.9 y MSVC 2017

masrtis
fuente
2

Personalmente, prefiero las funciones estáticas a los espacios de nombres sin nombre por los siguientes motivos:

  • Es obvio y claro desde la definición de la función solo que es privado para la unidad de traducción donde se compila. Con el espacio de nombres sin nombre, es posible que deba desplazarse y buscar para ver si una función está en un espacio de nombres.

  • Las funciones en los espacios de nombres pueden ser tratadas como externas por algunos compiladores (más antiguos). En VS2017 todavía son externos. Por esta razón, incluso si una función está en un espacio de nombres sin nombre, es posible que desee marcarlos como estáticos.

  • Las funciones estáticas se comportan de manera muy similar en C o C ++, mientras que los espacios de nombres sin nombre son obviamente solo C ++. los espacios de nombres sin nombre también agregan un nivel adicional en la sangría y eso no me gusta :)

Entonces, me alegra ver que el uso de static para funciones ya no está en desuso .

Pavel P
fuente
Se supone que las funciones en espacios de nombres anónimos tienen enlaces externos. Simplemente están destrozados para hacerlos únicos. Solo la staticpalabra clave realmente aplica el enlace local a una función. Además, ¿seguramente solo un loco loco realmente agregaría sangría para espacios de nombres?
Roflcopter4
0

Al haber aprendido esta característica hace un momento mientras leía su pregunta, solo puedo especular. Esto parece proporcionar varias ventajas sobre una variable estática a nivel de archivo:

  • Los espacios de nombres anónimos se pueden anidar entre sí, proporcionando múltiples niveles de protección de los que los símbolos no pueden escapar.
  • Se pueden colocar varios espacios de nombres anónimos en el mismo archivo fuente, creando en efecto diferentes ámbitos de nivel estático dentro del mismo archivo.

Me interesaría saber si alguien ha usado espacios de nombres anónimos en código real.

El comodoro Jaeger
fuente
44
Buenas especulaciones, pero equivocadas. El alcance de estos espacios de nombres abarca todo el archivo.
Konrad Rudolph
No es exactamente cierto, si define un espacio de nombres anónimo dentro de otro espacio de nombres, todavía solo tiene un ancho de archivo y solo puede verse como si estuviera dentro de ese espacio de nombres. Intentalo.
Greg Rogers
Podría estar equivocado, pero supongo que no, no es de todo el archivo: solo es accesible al código después del espacio de nombres anónimo. Esta es una cosa sutil, y por lo general, no me gustaría contaminar una fuente con múltiples espacios de nombres anónimos ... Aún así, esto puede tener usos.
paercebal
0

La diferencia es el nombre del identificador destrozado ( _ZN12_GLOBAL__N_11bEvs _ZL1b, que realmente no importa, pero ambos se ensamblan en símbolos locales en la tabla de símbolos (ausencia de .globaldirectiva 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

En cuanto a un espacio de nombres anónimo anidado:

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

Todos los espacios de nombres anónimos de primer nivel en la unidad de traducción se combinan entre sí. Todos los espacios de nombres anónimos de segundo nivel en la unidad de traducción se combinan entre sí.

También puede tener un espacio de nombres anidado (en línea) en un espacio de nombres anónimo

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

También puede tener espacios de nombres en línea anónimos, pero por lo que puedo decir, inlineen un espacio de nombres anónimo tiene un efecto 0

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

_ZL1b: _Zsignifica que este es un identificador destrozado. Lsignifica que es un símbolo local a través de static. 1es la longitud del identificador by luego el identificadorb

_ZN12_GLOBAL__N_11aE _Zsignifica que este es un identificador destrozado. Nsignifica que este es un espacio de nombres 12es la longitud del nombre de espacio de nombres anónimo _GLOBAL__N_1, luego el nombre de espacio de nombres anónimo _GLOBAL__N_1, luego 1es la longitud del identificador a, aes el identificador ay Ecierra el identificador que reside en un espacio de nombres.

_ZN12_GLOBAL__N_11A1aE es el mismo que el anterior, excepto que hay otro nivel de espacio de nombres en él 1A

Lewis Kelsey
fuente