¿Existe una mejor manera de expresar espacios de nombres anidados en C ++ dentro del encabezado?

97

Cambié de C ++ a Java y C # y creo que el uso de espacios de nombres / paquetes es mucho mejor allí (bien estructurado). Luego volví a C ++ e intenté usar los espacios de nombres de la misma manera, pero la sintaxis requerida es horrible dentro del archivo de encabezado.

namespace MyCompany
{
    namespace MyModule
    {
        namespace MyModulePart //e.g. Input
        {
            namespace MySubModulePart
            {
                namespace ...
                {
                    public class MyClass    

Lo siguiente también me parece extraño (para evitar la sangría profunda):

namespace MyCompany
{
namespace MyModule
{
namespace MyModulePart //e.g. Input
{
namespace MySubModulePart
{
namespace ...
{
     public class MyClass
     {

¿Existe una forma más corta de expresar lo anterior? Me falta algo como

namespace MyCompany::MyModule::MyModulePart::...
{
   public class MyClass

Actualizar

Ok, algunos dicen que el concepto de uso en Java / C # y C ++ es diferente. De Verdad? Creo que la carga de clases (dinámica) no es el único propósito de los espacios de nombres (esta es una perspectiva razonada muy técnica). ¿Por qué no debería usarlo para una legibilidad y estructuración, por ejemplo, pensar en "IntelliSense"?

Actualmente, no hay lógica / pegamento entre un espacio de nombres y lo que puede encontrar allí. Java y C # hacen esto mucho mejor ... ¿Por qué incluir <iostream>y tener espacio de nombres std? Ok, si dices que la lógica debería depender del encabezado para incluir, ¿por qué #include no usa una sintaxis amigable con "IntelliSense" como #include <std::io::stream>o <std/io/stream>? Creo que la estructuración que falta en las bibliotecas predeterminadas es una debilidad de C ++ en comparación con Java / C #.

Si la exclusividad de los conflictos ávidos es un punto (que también es un punto de C # y Java), una buena idea es usar el nombre del proyecto o el nombre de la empresa como espacio de nombres, ¿no lo crees?

Por un lado, se dice que C ++ es el más flexible ... ¿pero todos dijeron "no hagas esto"? Me parece que C ++ puede hacer muchas cosas, pero tiene una sintaxis horrible incluso para las cosas más fáciles en muchos casos en comparación con C #.

Actualización 2

La mayoría de los usuarios dicen que no tiene sentido crear un anidamiento más profundo que dos niveles. Ok, ¿qué pasa con los espacios de nombres de Windows :: UI :: Xaml y Windows :: UI :: Xaml :: Controls :: Primitives en el desarrollo de Win8? Creo que el uso de espacios de nombres por parte de Microsoft tiene sentido y, de hecho, es más profundo que solo 2 niveles. Creo que las bibliotecas / proyectos más grandes necesitan un anidamiento más profundo (odio los nombres de clases como ExtraLongClassNameBecauseEveryThingIsInTheSameNameSpace ... entonces también podría poner todo en el espacio de nombres global).

Actualización 3 - Conclusión

La mayoría dice "no lo hagas", pero ... incluso el impulso tiene un anidamiento más profundo que uno o dos niveles. Sí, es una biblioteca, pero: si desea un código reutilizable, trate su propio código como una biblioteca que le daría a otra persona. También utilizo un anidamiento más profundo con fines de descubrimiento utilizando espacios de nombres.

Beachwalker
fuente
3
¿Es un abuso de namespacepalabras clave?
Nawaz
4
los espacios de nombres y los sistemas de módulos c # / java no tienen el mismo propósito, por lo que no debe intentar usarlos de la misma manera. y no, no hay una sintaxis más simple, simplemente porque no tiene sentido proporcionar una sintaxis para hacer las cosas más fáciles de hacer, que no están destinadas a hacer.
PlasmaHH
@PlasmaHH ... ¿entonces la debilidad es la estructuración faltante de la librería estándar de C ++? (vea mi ejemplo simple dentro de la actualización)
Beachwalker
@Stegi: Si puede dar argumentos sólidos por qué falta y qué beneficios sólidos obtendríamos de tal estructuración, podríamos hablar sobre posibles debilidades. Hasta entonces, llamaría confuso en el mejor de los casos el anidamiento interminable de paquetes de Java.
PlasmaHH
3
@PlasmaHH Intellisense y otros ayudantes para / después de la inclusión del encabezado (paquete). Los proyectos grandes dentro de una empresa pueden necesitar más de un anidamiento (por ejemplo, vw :: golflib :: io) para una declaración clara de qué contiene un espacio de nombres en qué "alcance". Bueno, podrías usar vw :: pero si el espacio de nombres está destinado a evitar conflictos, ¿por qué son tan horribles de declarar? Esto termina hasta el punto de que nadie lo usa o simplemente usa un espacio de nombres con una profundidad de uno (como a menudo se sugiere).
Beachwalker

Respuestas:

130

C ++ 17 podría simplificar la definición del espacio de nombres anidado:

namespace A::B::C {
}

es equivalente a

namespace A { namespace B { namespace C {
} } }

Consulte (8) en la página de espacio de nombres en cppreference:
http://en.cppreference.com/w/cpp/language/namespace

W1M0R
fuente
5
... habilitado por el interruptor del compilador/std:c++latest
luna soleada
3
Tenga en cuenta que si va a usar /std:c++latestVisual Studio 2015 y también usa Boost, puede encontrar errores de compilador muy místicos cuando incluya algunos encabezados de Boost. Enfrenté este problema como se describe en esta pregunta de StackOverflow
Vivit
1
Funciona como está, espacio de nombres A :: B :: C. He probado con g ++ - 6.0
ervinbosenbacher
30

Para evitar una sangría realmente profunda, generalmente lo hago de esta manera:

namespace A { namespace B { namespace C
{
    class X
    {
        // ...
    };
}}}
Kurt Hutchinson
fuente
2
JFYI clang-formatno puede formatear eso ya que muestra clang.llvm.org/docs/ClangFormatStyleOptions.html (NamespaceIndentation)
KindDragon
17

Apoyo completamente la respuesta de Peterchen, pero quiero agregar algo que aborde otra parte de su pregunta.

La declaración de espacios de nombres es uno de los casos muy raros en C ++ en los que realmente me gusta el uso de #defines.

#define MY_COMPANY_BEGIN  namespace MyCompany { // begin of the MyCompany namespace
#define MY_COMPANY_END    }                     // end of the MyCompany namespace
#define MY_LIBRARY_BEGIN  namespace MyLibrary { // begin of the MyLibrary namespace
#define MY_LIBRARY_END    }                     // end of the MyLibrary namespace

Esto también elimina la necesidad de comentarios cerca de la llave de cierre del espacio de nombres (¿Alguna vez se desplazó hacia abajo hasta la parte inferior de un archivo fuente grande e intentó agregar / eliminar / equilibrar llaves a las que les faltaban comentarios sobre qué llave cierra qué alcance? No es divertido .).

MY_COMPANY_BEGIN
MY_LIBRARY_BEGIN

class X { };

class Y { };

MY_LIBRARY_END
MY_COMPANY_END

Si desea poner todas las declaraciones de espacio de nombres en una sola línea, puede hacerlo también con un poco de magia de preprocesador (bastante fea):

// helper macros for variadic macro overloading
#define VA_HELPER_EXPAND(_X)                    _X  // workaround for Visual Studio
#define VA_COUNT_HELPER(_1, _2, _3, _4, _5, _6, _Count, ...) _Count
#define VA_COUNT(...)                           VA_HELPER_EXPAND(VA_COUNT_HELPER(__VA_ARGS__, 6, 5, 4, 3, 2, 1))
#define VA_SELECT_CAT(_Name, _Count, ...)       VA_HELPER_EXPAND(_Name##_Count(__VA_ARGS__))
#define VA_SELECT_HELPER(_Name, _Count, ...)    VA_SELECT_CAT(_Name, _Count, __VA_ARGS__)
#define VA_SELECT(_Name, ...)                   VA_SELECT_HELPER(_Name, VA_COUNT(__VA_ARGS__), __VA_ARGS__)

// overloads for NAMESPACE_BEGIN
#define NAMESPACE_BEGIN_HELPER1(_Ns1)             namespace _Ns1 {
#define NAMESPACE_BEGIN_HELPER2(_Ns1, _Ns2)       namespace _Ns1 { NAMESPACE_BEGIN_HELPER1(_Ns2)
#define NAMESPACE_BEGIN_HELPER3(_Ns1, _Ns2, _Ns3) namespace _Ns1 { NAMESPACE_BEGIN_HELPER2(_Ns2, _Ns3)

// overloads for NAMESPACE_END
#define NAMESPACE_END_HELPER1(_Ns1)               }
#define NAMESPACE_END_HELPER2(_Ns1, _Ns2)         } NAMESPACE_END_HELPER1(_Ns2)
#define NAMESPACE_END_HELPER3(_Ns1, _Ns2, _Ns3)   } NAMESPACE_END_HELPER2(_Ns2, _Ns3)

// final macros
#define NAMESPACE_BEGIN(_Namespace, ...)    VA_SELECT(NAMESPACE_BEGIN_HELPER, _Namespace, __VA_ARGS__)
#define NAMESPACE_END(_Namespace, ...)      VA_SELECT(NAMESPACE_END_HELPER,   _Namespace, __VA_ARGS__)

Ahora puedes hacer esto:

NAMESPACE_BEGIN(Foo, Bar, Baz)

class X { };

NAMESPACE_END(Baz, Bar, Foo) // order doesn't matter, NAMESPACE_END(a, b, c) would work equally well

Foo::Bar::Baz::X x;

Para anidar a más de tres niveles, tendría que agregar macros auxiliares hasta el recuento deseado.

Max Truxa
fuente
Por mucho que no me guste #define, estoy bastante impresionado con la magia del preprocesador ... solo si no tengo que agregar macros de ayuda adicionales para un anidamiento más profundo ... bueno, no lo voy a usar de todos modos, así que ... .
galdin
12

Los espacios de nombres C ++ se utilizan para agrupar interfaces, no para dividir componentes o expresar división política.

El estándar hace todo lo posible para prohibir el uso de espacios de nombres similar al de Java. Por ejemplo, los alias de espacios de nombres proporcionan una forma de utilizar fácilmente nombres de espacios de nombres largos o profundamente anidados.

namespace a {
namespace b {
namespace c {}
}
}

namespace nsc = a::b::c;

Pero namespace nsc {}entonces sería un error, porque un espacio de nombres solo se puede definir usando su nombre de espacio de nombres original . Básicamente, el estándar facilita las cosas para el usuario de una biblioteca de este tipo, pero dificulta al implementador . Esto desalienta a las personas a escribir tales cosas, pero mitiga los efectos si lo hacen.

Debe tener un espacio de nombres por interfaz definido por un conjunto de clases y funciones relacionadas. Las subinterfaces internas u opcionales pueden entrar en espacios de nombres anidados. Pero más de dos niveles de profundidad debería ser una señal de alerta muy seria.

Considere la posibilidad de utilizar caracteres de subrayado y prefijos de identificador cuando ::no se necesite el operador.

Potatoswatter
fuente
17
Ok, ¿qué pasa con los espacios de nombres de Windows :: UI :: Xaml y Windows :: UI :: Xaml :: Controls :: Primitives en el desarrollo de Win8? Creo que el uso de espacios de nombres por parte de Microsoft tiene sentido y, de hecho, es más profundo que solo 2 niveles.
Beachwalker
2
Usar menos de 2 niveles es una señal de alerta y usar 3 o 4 está perfectamente bien. Intentar lograr una jerarquía de espacios de nombres plana cuando no tiene sentido frustra el propósito mismo de los espacios de nombres: evitar conflictos de nombres. Estoy de acuerdo en que debe tener un nivel para una interfaz y otro para subinterfaces e internos. Pero en torno a eso, necesita al menos un nivel más para el espacio de nombres de la empresa (para pequeñas y medianas empresas) o dos para la empresa y la división (para las grandes empresas). De lo contrario, los espacios de nombres de su interfaz chocarán con los de otras interfaces con el mismo nombre desarrolladas en otros lugares
Kaiserludi
@Kaiserludi ¿Cuál es la ventaja técnica de company::divisionover company_division?
Potatoswatter
@Potatoswatter Inside company :: anotherDivsion puede usar la 'división' más corta. para referirse a company :: division incluso dentro de los encabezados donde debe evitar enérgicamente contaminar los espacios de nombres de nivel superior mediante el uso de 'using namespace'. Fuera del espacio de nombres de la empresa, todavía puede hacer un 'uso de la empresa del espacio de nombres'; cuando los nombres de las divisiones no chocan con ningún otro espacio de nombres en su alcance, pero cuando los nombres de la interfaz dentro de algunos de los espacios de nombres de la división chocan de modo que no puede hacer 'usando el espacio de nombres company_division;'.
Kaiserludi
3
@Potatoswatter El punto es que lo obtienes prácticamente gratis (company :: division no es más largo que company_division) y no tienes que definir un alias de espacio de nombres adicional primero para usarlo.
Kaiserludi
6

No, y por favor no hagas eso.

El propósito de los espacios de nombres es principalmente resolver conflictos en el espacio de nombres global.

Un propósito secundario es la abreviatura local de símbolos; por ejemplo, un UpdateUImétodo complejo puede utilizar un using namespace WndUIpara utilizar símbolos más cortos.

Estoy en un proyecto 1.3MLoc, y los únicos espacios de nombres que tenemos son:

  • bibliotecas COM externas importadas (principalmente para aislar conflictos de encabezado entre #importy #include windows.h)
  • Un nivel de espacios de nombres de "API públicas" para ciertos aspectos (UI, acceso a la base de datos, etc.)
  • Espacios de ModuleDetailHereBeTygersnombres de "detalles de implementación" que no forman parte de la API pública (espacios de nombres anónimos en .cpp o espacios de nombres en bibliotecas de solo encabezado)
  • Las enumeraciones son el mayor problema en mi experiencia. Contaminan como locos.
  • Sigo sintiendo que son demasiados espacios de nombres

En este proyecto, los nombres de clases, etc. utilizan un código de "región" de dos o tres letras (por ejemplo, en CDBNodelugar de DB::CNode). Si prefiere lo último, hay espacio para un segundo nivel de espacios de nombres "públicos", pero no más.

Las enumeraciones específicas de la clase, etc.pueden ser miembros de esas clases (aunque estoy de acuerdo en que esto no siempre es bueno, y a veces es difícil decir si debería hacerlo)

Rara vez es necesario un espacio de nombres de "empresa", excepto si tiene grandes problemas con bibliotecas de terceros que se distribuyen como binarias, no proporcionan su propio espacio de nombres y no se pueden poner fácilmente en uno (por ejemplo, en un binario distribución). Aún así, en mi experiencia, forzarlos a entrar en un espacio de nombres es mucho más fácil de hacer.


[editar] Según la pregunta de seguimiento de Stegi:

Ok, ¿qué pasa con los espacios de nombres de Windows :: UI :: Xaml y Windows :: UI :: Xaml :: Controls :: Primitives en el desarrollo de Win8? Creo que el uso de espacios de nombres por parte de Microsoft tiene sentido y de hecho es más profundo que solo 2 niveles

Lo siento si no fui lo suficientemente claro: dos niveles no es un límite estricto y más no es intrínsecamente malo. Solo quería señalar que rara vez se necesitan más de dos, según mi experiencia, incluso en una base de código grande. Anidar más profundo o menos profundo es una compensación.

Ahora, el caso de Microsoft es posiblemente diferente. Es de suponer que un equipo mucho más grande y todo el código es una biblioteca.

Asumiría que Microsoft está imitando aquí el éxito de la biblioteca .NET, donde los espacios de nombres contribuyen a la visibilidad de la extensa biblioteca. (.NET tiene alrededor de 18000 tipos).

Además, supongo que hay un símbolo óptimo (orden de magnitud de) en un espacio de nombres. digamos, 1 no tiene sentido, 100 suena bien, 10000 es claramente demasiado.


TL; DR: Es una compensación y no tenemos cifras concretas. Juega seguro, no exageres en ninguna dirección. El "No hagas eso" proviene simplemente del "Tienes problemas con eso, yo tendría problemas con eso y no veo una razón por la que lo necesites".

Peterchen
fuente
8
Ok, ¿qué pasa con los espacios de nombres de Windows :: UI :: Xaml y Windows :: UI :: Xaml :: Controls :: Primitives en el desarrollo de Win8? Creo que el uso de espacios de nombres por parte de Microsoft tiene sentido y, de hecho, es más profundo que solo 2 niveles.
Beachwalker
2
Si necesito constantes de acceso global, me gusta ponerlas en un espacio de nombres con un nombre como Constants, luego crear espacios de nombres anidados con nombres apropiados para categorizar las constantes; si es necesario, utilizo más espacios de nombres para evitar colisiones de nombres. Este Constantsespacio de nombres está contenido en un espacio de nombres general para el código del sistema del programa, con un nombre como SysData. Esto crea un nombre completo que contiene tres o cuatro espacios de nombres (como SysData::Constants::ErrorMessages, SysData::Constants::Ailments::Bitflags, o SysData::Defaults::Engine::TextSystem).
Justin Time - Reincorporación a Monica
1
Cuando las constantes se requieren en el código real, cualquier función que las necesite usa una usingdirectiva para traer los nombres apropiados, minimizando la posibilidad de conflictos de nombres. Encuentro que mejora la legibilidad y ayuda a documentar las dependencias de cualquier bloque de código. Aparte de las constantes, tiendo a intentar mantenerlo en dos espacios de nombres si es posible (como SysData::Exceptionsy SysData::Classes).
Justin Time - Reincorporación a Monica
2
En general, diría que en casos generales, es mejor usar una cantidad mínima de espacios de nombres anidados, pero si necesita objetos globales por alguna razón (ya sea constante o mutable, preferiblemente el primero), se deben usar múltiples espacios de nombres anidados. para separarlos en categorías apropiadas, tanto para documentar su uso como para minimizar posibles colisiones de nombres.
Justin Time - Reincorporación a Monica
2
-1 para "por favor, no hagas esto" sin razones objetivas por qué (a pesar de las aclaraciones posteriores). El lenguaje admite espacios de nombres anidados y un proyecto puede tener buenas razones para usarlos. Una discusión de posibles tales razones y cualquier desventaja objetiva y concreta de hacerlo revertiría mi voto negativo.
TypeIA
4

Aquí una cita de los documentos de Lzz (Lazy C ++):

Lzz reconoce las siguientes construcciones de C ++:

definición de espacio de nombres

Un espacio de nombres sin nombre y todas las declaraciones adjuntas se envían al archivo de origen. Esta regla anula todas las demás.

El nombre de un espacio de nombres con nombre puede estar calificado.

   namespace A::B { typedef int I; }

es equivalente a:

   namespace A { namespace B { typedef int I; } }

Por supuesto, la calidad de las fuentes que dependen de tales herramientas es discutible ... Yo diría que es más una curiosidad, mostrar que la enfermedad sintáctica inducida por C ++ puede tomar muchas formas (yo también tengo la mía ...)

CapelliC
fuente
2

Ambos estándares (C ++ 2003 y C ++ 11) son muy explícitos en cuanto a que el nombre del espacio de nombres es un identificador. Esto significa que se requieren encabezados anidados explícitos.

Mi impresión es que no es un gran problema permitir colocar un identificador calificado además de un nombre simple del espacio de nombres, pero por alguna razón esto no está permitido.

Kirill Kobelev
fuente
1

Puede utilizar esta sintaxis:

namespace MyCompany {
  namespace MyModule {
    namespace MyModulePart //e.g. Input {
      namespace MySubModulePart {
        namespace ... {
          class MyClass;
        }
      }
    }
  }
}

// Here is where the magic happens
class MyCompany::MyModule::MyModulePart::MySubModulePart::MyYouGetTheIdeaModule::MyClass {
    ...
};

Tenga en cuenta que esta sintaxis es válida incluso en C ++ 98 y es casi similar a lo que ahora está disponible en C ++ 17 con definiciones de espacio de nombres anidadas .

¡Feliz desanidación!

Fuentes:

smac89
fuente
Esa es la sintaxis mencionada en la pregunta en la que se busca una mejor solución. Ahora, con C ++ 17 hay una alternativa válida disponible como se indica en la respuesta aceptada. Lo siento, vota en contra por no leer la pregunta y la respuesta.
Beachwalker
@Beachwalker no nos dejemos atrapar por la sintaxis. La declaración de espacio de nombres anterior también podría ser la misma que en la respuesta aceptada. El punto principal que quería resaltar con esta respuesta es lo que el OP dijo que se perdió y lo que hice debajo de ese lío de espacio de nombres. Por lo que puedo ver, todos parecen haberse centrado en declarar todo dentro del espacio de nombres, mientras que mi respuesta lo saca del lío anidado y estoy seguro de que OP habría apreciado esta sintaxis si alguien la hubiera mencionado hace 4 años cuando esta pregunta fue preguntado primero.
smac89
1

Este documento cubre bastante bien el tema: Papel de espacio de nombres

Lo que básicamente se reduce a esto. Cuanto más largos sean los espacios de nombres, más probabilidades habrá de que las personas utilicen la using namespacedirectiva.

Entonces, mirando el siguiente código, puede ver un ejemplo en el que esto lo perjudicará:

namespace abc { namespace testing {
    class myClass {};
}}

namespace def { namespace testing {
    class defClass { };
}}

using namespace abc;
//using namespace def;

int main(int, char**) {
    testing::myClass classInit{};
}

Este código se compilará bien, sin embargo, si descomenta la línea //using namespace def; , el espacio de nombres de "prueba" se volverá ambiguo y tendrá colisiones de nombres. Esto significa que su base de código puede pasar de estable a inestable al incluir una biblioteca de terceros.

En C #, incluso si lo usara using abc;y using def;el compilador fuera capaz de reconocerlo testing::myClasso incluso solo estuviera myClassen el abc::testingespacio de nombres, pero C ++ no reconocería esto y se detecta como una colisión.

Cuestionable
fuente
0

Si, tendrás que hacerlo como

namespace A{ 
namespace B{
namespace C{} 
} 
}

Sin embargo, está intentando utilizar los espacios de nombres de una forma en que no se supone que se utilicen. Marque esta pregunta, tal vez la encuentre útil.

SingerOfTheFall
fuente
-1

[EDITAR:]
Dado que los espacios de nombres anidados de c ++ 17 son compatibles como una característica de idioma estándar ( https://en.wikipedia.org/wiki/C%2B%2B17 ). A partir de ahora, esta función no es compatible con g ++ 8, pero se puede encontrar en el compilador clang ++ 6.0.


[CONCLUSIÓN:]
Úselo clang++6.0 -std=c++17como su comando de compilación predeterminado. Entonces todo debería funcionar bien y podrá compilar namespace OuterNS::InnerNS1::InnerNS2 { ... }en sus archivos.


[RESPUESTA ORIGINAL:]
Dado que esta pregunta es un poco antigua, asumiré que ha seguido adelante. Pero para otros, que todavía están buscando una respuesta, se me ocurrió la siguiente idea:

Búferes de Emacs que muestran el archivo principal, los archivos de espacio de nombres, el comando / resultado de compilación y la ejecución de la línea de comandos.

(¿Puedo hacer un anuncio para Emacs aquí :)?) Publicar una imagen es mucho más fácil y más legible que simplemente publicar un código. No tengo la intención de proporcionar una respuesta completa de todos los casos de esquina, simplemente quise dar algo de inspiración. (Soy totalmente compatible con C # y creo que en muchos casos C ++ debería adoptar algunas características de programación orientada a objetos, ya que C # es popular principalmente debido a su facilidad de uso comparada).

ワ イ き ん ぐ
fuente
Actualización: dado que C ++ 17 ha anidado el espacio de nombres ( nuonsoft.com/blog/2017/08/01/c17-nested-namespaces ), parece que mi respuesta ya no es relevante, a menos que esté utilizando versiones anteriores de C ++ .
ワ イ き ん ぐ
1
Por mucho que me encanta Emacs, publicar una imagen no es lo ideal. Elude la búsqueda / indexación de texto y también hace que su respuesta sea de difícil acceso para los visitantes con discapacidad visual.
Heinrich apoya a Monica el