Los espacios de nombres en línea son una característica de versiones de la biblioteca similar a las versiones de símbolos , pero se implementan exclusivamente en el nivel C ++ 11 (es decir, multiplataforma) en lugar de ser una característica de un formato ejecutable binario específico (es decir, específico de la plataforma).
Es un mecanismo por el cual el autor de una biblioteca puede hacer que un espacio de nombres anidado se vea y actúe como si todas sus declaraciones estuvieran en el espacio de nombres circundante (los espacios de nombres en línea se pueden anidar, por lo que los nombres "más anidados" se filtran hasta el primer no -inline espacio de nombres y se ven y actúan como si sus declaraciones estuvieran en cualquiera de los espacios de nombres intermedios, también).
Como ejemplo, considere la implementación de STL de vector
. Si tuviéramos espacios de nombres en línea desde el comienzo de C ++, entonces en C ++ 98 el encabezado <vector>
podría tener este aspecto:
namespace std {
#if __cplusplus < 1997L // pre-standard C++
inline
#endif
namespace pre_cxx_1997 {
template <class T> __vector_impl; // implementation class
template <class T> // e.g. w/o allocator argument
class vector : __vector_impl<T> { // private inheritance
// ...
};
}
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
# if __cplusplus == 1997L // C++98/03
inline
# endif
namespace cxx_1997 {
// std::vector now has an allocator argument
template <class T, class Alloc=std::allocator<T> >
class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
// ...
};
// and vector<bool> is special:
template <class Alloc=std::allocator<bool> >
class vector<bool> {
// ...
};
};
#endif // C++98/03 or later
} // namespace std
Dependiendo del valor de __cplusplus
, vector
se elige una u otra implementación. Si su código base fue escrito en versiones anteriores a C ++ 98 veces, y encuentra que la versión de C ++ 98 le vector
está causando problemas cuando actualiza su compilador, "todo" que tiene que hacer es encontrar las referencias std::vector
en su base de código y reemplácelos por std::pre_cxx_1997::vector
.
Llegue al siguiente estándar, y el proveedor de STL simplemente repite el procedimiento nuevamente, presentando un nuevo espacio de nombres std::vector
con emplace_back
soporte (que requiere C ++ 11) e integrando ese iff __cplusplus == 201103L
.
Bien, entonces ¿por qué necesito una nueva función de idioma para esto? Ya puedo hacer lo siguiente para tener el mismo efecto, ¿no?
namespace std {
namespace pre_cxx_1997 {
// ...
}
#if __cplusplus < 1997L // pre-standard C++
using namespace pre_cxx_1997;
#endif
#if __cplusplus >= 1997L // C++98/03 or later
// (ifdef'ed out b/c it probably uses new language
// features that a pre-C++98 compiler would choke on)
namespace cxx_1997 {
// ...
};
# if __cplusplus == 1997L // C++98/03
using namespace cxx_1997;
# endif
#endif // C++98/03 or later
} // namespace std
Dependiendo del valor de __cplusplus
, obtengo una u otra de las implementaciones.
Y estarías casi en lo correcto.
Considere el siguiente código de usuario válido de C ++ 98 (ya estaba permitido especializar completamente las plantillas que viven en el espacio std
de nombres en C ++ 98):
// I don't trust my STL vendor to do this optimisation, so force these
// specializations myself:
namespace std {
template <>
class vector<MyType> : my_special_vector<MyType> {
// ...
};
template <>
class vector<MyOtherType> : my_special_vector<MyOtherType> {
// ...
};
// ...etc...
} // namespace std
Este es un código perfectamente válido en el que el usuario proporciona su propia implementación de un vector para un conjunto de tipos en el que aparentemente conoce una implementación más eficiente que la que se encuentra en (su copia de) el STL.
Pero : cuando se especializa una plantilla, debe hacerlo en el espacio de nombres en el que se declaró. El Estándar dice que vector
se declara en el espacio de nombres std
, por lo que es allí donde el usuario espera especializar el tipo.
Este código funciona con un espacio de nombres no versionado std
, o con la función de espacio de nombres en línea C ++ 11, pero no con el truco de versiones utilizado using namespace <nested>
, porque eso expone el detalle de implementación de que el verdadero espacio de nombres en el que vector
se definió no estaba std
directamente.
Hay otros agujeros por los cuales puede detectar el espacio de nombres anidado (vea los comentarios a continuación), pero los espacios de nombres en línea los conectan a todos. Y eso es todo lo que hay que hacer. Inmensamente útil para el futuro, pero AFAIK the Standard no prescribe nombres de espacios de nombres en línea para su propia biblioteca estándar (sin embargo, me encantaría que se demuestre lo contrario), por lo que solo se puede usar para bibliotecas de terceros, no el estándar en sí (a menos que los proveedores del compilador acuerden un esquema de nombres).
using namespace V99;
no funciona en el ejemplo de Stroustrup.std::cxx_11
. No todos los compiladores siempre implementarán todas las versiones antiguas de las bibliotecas estándar, a pesar de que en este momento es tentador pensar que sería una carga muy pequeña exigir que las implementaciones existentes dejen en la versión anterior cuando agreguen la nueva, ya que de hecho todas son de todos modos Supongo que lo que el estándar podría haber hecho útil es hacerlo opcional, pero con un nombre estándar si está presente.using namespace A
en un espacio de nombres B hace que los nombres en el espacio de nombres B oculten nombres en el espacio de nombres A si lo buscaB::name
, no es así con espacios de nombres en línea).ifdef
s para la implementación de vector completo? Todas las implementaciones estarían en un espacio de nombres, pero solo una de ellas se definirá después del preprocesamientousing
palabra clave).http://www.stroustrup.com/C++11FAQ.html#inline-namespace (un documento escrito y mantenido por Bjarne Stroustrup, quien crees que debería tener en cuenta la mayoría de las motivaciones para la mayoría de las características de C ++ 11. )
De acuerdo con eso, es permitir el control de versiones para la compatibilidad con versiones anteriores. Define múltiples espacios de nombres internos y crea el más reciente
inline
. O de todos modos, el predeterminado para las personas que no se preocupan por el versionado. Supongo que la más reciente podría ser una versión futura o de vanguardia que aún no es predeterminada.El ejemplo dado es:
No veo de inmediato por qué no se coloca
using namespace V99;
dentro del espacio de nombresMine
, pero no tengo que entender completamente el caso de uso para tomar la palabra de Bjarne en la motivación del comité.fuente
f(1)
versión se llamaría desde elV99
espacio de nombres en línea ?using namespace Mine;
, y elMine
espacio de nombres contiene todo, desde el espacio de nombres en líneaMine::V99
.inline
del archivoV99.h
en la versión que incluyeV100.h
. También modificaMine.h
al mismo tiempo, por supuesto, para agregar una inclusión adicional.Mine.h
es parte de la biblioteca, no parte del código del cliente.V100.h
, están instalando una biblioteca llamada "Mine". Hay 3 archivos de encabezado en la versión 99 de "Mine" -Mine.h
,V98.h
yV99.h
. Hay 4 archivos de cabecera en la versión 100 de "Mine" -Mine.h
,V98.h
,V99.h
yV100.h
. La disposición de los archivos de encabezado es un detalle de implementación que es irrelevante para los usuarios. Si descubren algún problema de compatibilidad, lo que significa que deben usar específicamenteMine::V98::f
parte de todo o parte de su código, pueden mezclar llamadasMine::V98::f
desde el código antiguo con llamadas alMine::f
código recién escrito.Mine
, en lugar de tener que especializarse enMine::V99
oMine::V98
.Además de todas las otras respuestas.
El espacio de nombres en línea se puede utilizar para codificar información ABI o versión de las funciones en los símbolos. Es por esta razón que se utilizan para proporcionar compatibilidad ABI hacia atrás. Los espacios de nombres en línea le permiten inyectar información en el nombre mutilado (ABI) sin alterar la API porque solo afectan al nombre del símbolo del vinculador.
Considere este ejemplo:
Supongamos que escribe una función
Foo
que toma una referencia a un objetobar
y no devuelve nada.Decir en main.cpp
Si comprueba el nombre de su símbolo para este archivo después de compilarlo en un objeto.
Ahora, podría ser que
bar
se define como:Dependiendo del tipo de construcción,
bar
puede referirse a dos tipos / diseños diferentes con los mismos símbolos de enlace.Para evitar tal comportamiento, envolvemos nuestra estructura
bar
en un espacio de nombres en línea, donde dependiendo del tipo de construcción, el símbolo del enlazadorbar
será diferente.Entonces, podríamos escribir:
Ahora, si mira el archivo de objeto de cada objeto, construye uno usando la versión y otro con el indicador de depuración. Encontrará que los símbolos del enlazador también incluyen el nombre del espacio de nombres en línea. En este caso
Observe la presencia de
rel
ydbg
en los nombres de los símbolos.Ahora, si intenta vincular la depuración con el modo de liberación o viceversa, obtendrá un error de vinculador como contrario al error de tiempo de ejecución.
fuente
De hecho, descubrí otro uso para los espacios de nombres en línea.
Con Qt , obtienes algunas características adicionales y agradables
Q_ENUM_NS
que, a su vez, requieren que el espacio de nombres incluido tenga un metaobjeto, que se declara conQ_NAMESPACE
. Sin embargo, paraQ_ENUM_NS
que funcione, debe haber un correspondienteQ_NAMESPACE
en el mismo archivo ⁽¹⁾. Y solo puede haber uno, u obtienes errores de definición duplicados. Esto, efectivamente, significa que todas sus enumeraciones deben estar en el mismo encabezado. YuckO ... puede usar espacios de nombres en línea. Ocultar enumeraciones en un
inline namespace
hace que los metaobjetos tengan diferentes nombres destrozados, mientras que a los usuarios les parece que el espacio de nombres adicional no existe⁽²⁾.Por lo tanto, son útiles para dividir cosas en múltiples subespacios de nombres que se parecen a un espacio de nombres, si necesita hacerlo por alguna razón. Por supuesto, esto es similar a escribir
using namespace inner
en el espacio de nombres externo, pero sin la violación DRY de escribir dos veces el nombre del espacio de nombres interno.En realidad es peor que eso; tiene que estar en el mismo conjunto de llaves.
A menos que intente acceder al metaobjeto sin calificarlo por completo, pero el metaobjeto casi nunca se usa directamente.
fuente
Entonces, para resumir los puntos principales,
using namespace v99
yinline namespace
no eran lo mismo, el primero era una solución alternativa a las bibliotecas de versiones antes de que se introdujera una palabra clave dedicada (en línea) en C ++ 11 que solucionaba los problemas de usousing
, al tiempo que proporcionaba la misma funcionalidad de versiones. El usousing namespace
utilizado para causar problemas con ADL (aunque ahora parece que ADL sigue lasusing
directivas), y la especialización fuera de línea de una clase / función de biblioteca, etc. por parte del usuario no funcionaría si se realiza fuera del espacio de nombres verdadero (cuyo nombre el usuario no debería ni debería saberlo, es decir, el usuario tendría que usar B :: abi_v2 :: en lugar de solo B :: para que se resolviera la especialización).Esto mostrará una advertencia de análisis estático
first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]
. Pero si hace que el espacio de nombres A esté en línea, el compilador resuelve correctamente la especialización. Aunque, con las extensiones de C ++ 11, el problema desaparece.Las definiciones fuera de línea no se resuelven cuando se usan
using
; deben declararse en un bloque de espacio de nombres de extensión anidado / no anidado (lo que significa que el usuario necesita conocer la versión ABI nuevamente, si por alguna razón se les permitió proporcionar su propia implementación de una función).El problema desaparece cuando se hace B en línea.
Los otros
inline
espacios de nombres funcionales permiten al escritor de la biblioteca proporcionar una actualización transparente de la biblioteca 1) sin obligar al usuario a refactorizar el código con el nuevo nombre del espacio de nombres y 2) evitar la falta de verbosidad y 3) proporcionar abstracción de detalles irrelevantes de la API, mientras que 4) proporciona los mismos diagnósticos y comportamientos beneficiosos del enlazador que proporcionaría el uso de un espacio de nombres no en línea. Digamos que estás usando una biblioteca:Permite al usuario llamar
library::foo
sin necesidad de conocer o incluir la versión ABI en la documentación, que se ve más limpia. Usarlibrary::abiverison129389123::foo
se vería sucio.Cuando se realiza una actualización
foo
, es decir, agregar un nuevo miembro a la clase, no afectará a los programas existentes en el nivel API porque ya no usarán el miembro Y el cambio en el nombre del espacio de nombres en línea no cambiará nada en el nivel API porquelibrary::foo
seguirá funcionandoSin embargo, para los programas que se vinculan con él, debido a que el nombre del espacio de nombres en línea se divide en nombres de símbolos como un espacio de nombres normal, el cambio no será transparente para el vinculador. Por lo tanto, si la aplicación no se vuelve a compilar pero está vinculada con una nueva versión de la biblioteca, presentará un símbolo de
abi_v1
error que no se encuentra, en lugar de que realmente se vincule y luego cause un misterioso error lógico en tiempo de ejecución debido a la incompatibilidad ABI. Agregar un nuevo miembro causará compatibilidad ABI debido al cambio en la definición de tipo, incluso si no afecta el programa en tiempo de compilación (nivel de API).En este escenario:
Al igual que el uso de 2 espacios de nombres no en línea, permite vincular una nueva versión de la biblioteca sin necesidad de volver a compilar la aplicación, ya
abi_v1
que se descompondrá en uno de los símbolos globales y utilizará la definición de tipo correcta (antigua). Sin embargo, volver a compilar la aplicación hará que las referencias se resuelvanlibrary::abi_v2
.El uso
using namespace
es menos funcional que el usoinline
(ya que las definiciones fuera de línea no se resuelven) pero proporciona las mismas 4 ventajas que anteriormente. Pero la verdadera pregunta es, ¿por qué seguir usando una solución alternativa cuando ahora hay una palabra clave dedicada para hacerlo? Es una mejor práctica, menos detallada (tiene que cambiar 1 línea de código en lugar de 2) y deja en claro la intención.fuente