2010-02-04 11 views
7

entiendo que una biblioteca de C++ debe utilizar un espacio de nombres para evitar conflictos de nombres, pero como ya tengo a:¿El uso de un espacio de nombres C++ aumenta el acoplamiento?

  1. #include la cabecera correcta (o hacia adelante declare las clases que tengo la intención de usar)
  2. utilizar esas clases por su nombre

no hace estas dos parámetros inferir la misma información transmitida por un espacio de nombres. El uso de un espacio de nombres ahora introduce un tercer parámetro: el nombre completo. Si la implementación de la biblioteca cambia, ahora hay tres posibles cosas que necesito cambiar. ¿No es esto, por definición, un aumento en el acoplamiento entre el código de la biblioteca y mi código?


Por ejemplo, mira a Xerces-C: Se define una interfaz pura virtual llamado Parser dentro del espacio de nombres XERCES_CPP_NAMESPACE. Puedo hacer uso de la interfaz Parser en mi código al incluir el archivo de encabezado apropiado y luego importar el espacio de nombres using namespace XERCES_CPP_NAMESPACE o prologar declaraciones/definiciones con XERCES_CPP_NAMESPACE::.

A medida que el código evoluciona, quizás haya una necesidad de soltar Xerces a favor de un analizador diferente. Estoy parcialmente "protegido" del cambio en la implementación de la biblioteca por la interfaz pura-virtual (más aún si uso una fábrica para construir mi analizador), pero tan pronto como cambie de Xerces a otra cosa, necesito peine a través de mi código y cambie todos mis códigos using namespace XERCES_CPP_NAMESPACE y XERCES_CPP_NAMESPACE::Parser.


me encontré con esto recientemente cuando refactorizado un proyecto existente C++ para dividir-cabo algunas funciones útiles existentes en una biblioteca:

foo.h

class Useful; // Forward Declaration 

class Foo 
{ 
public: 

    Foo(const Useful& u); 
    ...snip... 

} 

foo. cpp

#include "foo.h" 
#include "useful.h" // Useful Library 

Foo::Foo(const Useful& u) 
{ 
    ... snip ... 
} 

En gran parte por ignorancia (y parcialmente por holgazanería) en ese momento, toda la funcionalidad de useful.lib se colocó en el espacio de nombres global.

Como el contenido de useful.lib crecieron (y más clientes comenzaron a utilizar la funcionalidad), se decidió a mover todo el código de useful.lib en su propio espacio de nombres llamado "useful".

Los archivos del cliente .cpp fueron fáciles de solucionar, simplemente agregue un using namespace useful;

foo.cpp

#include "foo.h" 
#include "useful.h" // Useful Library 

using namespace useful; 

Foo::Foo(const Useful& u) 
{ 
    ... snip ... 
} 

pero los archivos .h eran realmente mano de obra intensiva.En lugar de contaminar el espacio de nombres global poniendo using namespace useful; en los archivos de cabecera, envolví las declaraciones existentes hacia adelante en el espacio de nombres:

foo.h

namespace useful { 
    class Useful; // Forward Declaration 
} 

class Foo 
{ 
public: 

    Foo(const useful::Useful& u); 
    ...snip... 
} 

había docenas y docenas () de archivos y esto terminó siendo un gran dolor! No debería haber sido tan difícil. Claramente, hice algo mal con el diseño o la implementación.

Aunque sé que el código de la biblioteca debería estar en su propio espacio de nombres, ¿habría sido ventajoso para el código de la biblioteca permanezca en el espacio de nombres global, y en lugar de tratar de gestionar el #includes?

+2

"(b) Detalles de implementación inferidos por el espacio de nombres" - ¿Puede explicar esto con más detalle? No entiendo lo que eso significa. –

+6

Casi me quedé dormido antes de llegar a la pregunta, y todavía no entiendo por qué preguntas. ¿Consideraste que: utilizaste útil? Útil; –

+1

Eso es mucho texto. ¿Hay alguna versión condensada que pueda leer? – wheaties

Respuesta

10

Me parece que su problema se debe principalmente a la forma en que (ab) utiliza espacios de nombres, no debido a los espacios de nombres propios.

  1. Suena como usted está lanzando una gran cantidad de mínimamente relacionada con "cosas" en un espacio de nombres, en su mayoría (cuando llegue a fin de cuentas) porque sucede que han sido desarrollados por la misma persona. Al menos IMO, un espacio de nombres debe reflejar la organización lógica del código, no solo el accidente que un conjunto de utilidades pasó a ser escrito por la misma persona.

  2. Un nombre de espacio de nombre suele ser bastante largo y descriptivo para evitar más que la posibilidad más remota de una colisión. Por ejemplo, generalmente incluyo mi nombre, fecha de escritura y una breve descripción de la funcionalidad del espacio de nombres.

  3. La mayoría del código de cliente no necesita (y no debería) utilizar el nombre real del espacio de nombres directamente. En su lugar, debe definir un alias de espacio de nombres, y solo el nombre de alias debe usarse en la mayoría del código.

puntos poniendo dos y tres juntos, que pueden acabar con el código de algo como esto:

#include "jdate.h" 

namespace dt = Jerry_Coffin_Julian_Date_Dec_21_1999; 

int main() { 

    dt::Date date; 

    std::cout << "Please enter a date: " << std::flush; 
    std::cin>>date; 

    dt::Julian jdate(date); 
    std::cout << date << " is " 
       << jdate << " days after " 
       << dt::Julian::base_date() 
       << std::endl; 
    return 0; 
} 

Esto elimina (o al menos reduce drásticamente) de acoplamiento entre el código de cliente y una aplicación particular de las clases de fecha/hora. Por ejemplo, si quisiera volver a implementar las mismas clases de fecha/hora, podría ponerlas en un espacio de nombres diferente, y cambiar entre una y otra simplemente cambiando el alias y volviendo a compilar.

De hecho, he usado esto a veces como una especie de mecanismo de polimorfismo en tiempo de compilación. Por ejemplo, he escrito un par de versiones de una pequeña clase de "pantalla", una que muestra la salida en un cuadro de lista de Windows y otra que muestra la salida a través de iostremas. El código a continuación, utiliza un alias algo como:

#ifdef WINDOWED 
namespace display = Windowed_Display 
#else 
namespace display = Console_Display 
#endif 

El resto del código sólo utiliza display::whatever, así que mientras ambos espacios de nombres implementar toda la interfaz, puedo utilizar cualquiera de ellas, sin cambiar el resto del código en todos, y sin sobrecarga de tiempo de ejecución desde el uso de un puntero/referencia a una clase base con funciones virtuales para las implementaciones.

+1

* "como una especie de mecanismo de polimorfismo en tiempo de ejecución" * - ¿Debería ser * "tiempo de compilación" *? –

+0

@ gf: oops, sí. Gracias. Lo arreglaré. –

+0

+1 Gran sugerencia. –

6

(a) interfaces/clases/funciones de la biblioteca

No

más de lo que ya tiene. El uso de namespace -ed componentes de la biblioteca le ayuda a evitar la contaminación del espacio de nombres.

(b) ¿detalles de implementación inferidos por el espacio de nombres?

¿Por qué? Todo lo que debe incluir es un encabezado useful.h. La implementación debe estar oculta (y residir en el useful.cpp y probablemente en una forma de biblioteca dinámica).

Puede incluir de forma selectiva solo las clases que necesita desde useful.h teniendo using useful::Useful declaraciones.

0

Si tiene varias implementaciones de la biblioteca de "útil", entonces no es igualmente probable (si no está bajo su control) que utilizarían el mismo espacio de nombres, ya sea el mundial espacio de nombres o la útil espacio de nombres?

Dicho de otra manera, usar un espacio de nombre nombrado versus el espacio de nombre global no tiene nada que ver con qué tan "acoplado" está con una biblioteca/implementación.

Cualquier estrategia de evolución de biblioteca coherente debe mantener el mismo espacio de nombres para la API. La implementación podría utilizar diferentes espacios de nombres ocultos para usted, y estos podrían cambiar en diferentes implementaciones. No estoy seguro de si esto es lo que quiere decir con "detalles de implementación inferidos por el espacio de nombres".

0

no, no aumenta el acoplamiento. Como otros han dicho - no veo cómo el uso de espacio de nombres se filtra la implementación cabo

el consumidor puede optar por hacer

using useful; 
using useful::Foo; 
useful::Foo = new useful::Foo(); 

mi voto es siempre para el último - es la menos contaminante

El primero debe ser fuertemente desaconsejado (por el pelotón de fusilamiento)

+0

Presumiblemente para el primero que pretendía 'usar el espacio de nombres útil ; '? –

9

El espacio de nombres no tiene nada que ver con el acoplamiento. El mismo acoplamiento existe ya sea que lo llame useful::UsefulClass o simplemente UsefulClass.Ahora, el hecho de que haya necesitado hacer todo el trabajo de refactorización solo le indica en qué medida su código depende de su biblioteca.

para facilitar la expedición que podría haber escrito un encabezado forward (hay un par en el STL, seguramente puede encontrar en bibliotecas) como usefulfwd.h que sólo hacia adelante define la interfaz de biblioteca (o la implementación de clases o lo que sea necesario) . Pero esto no tiene nada que ver con el acoplamiento.

Aún así, el acoplamiento y los espacios de nombres no están relacionados. Una rosa podría oler como dulce con cualquier otro nombre, y tus clases están tan acopladas en cualquier otro espacio de nombres.

+1

" Ahora, el hecho de que haya necesitado hacer todo ese trabajo refactorizando solo le indica en qué medida su código depende de su biblioteca. "Ese es un buen punto. Hice el código en una biblioteca para que otros pudieran usarlo de ello, pero mi código original dependía en gran medida de esa funcionalidad. –

+1

+1 para la respuesta correcta –

0

Bueno, la verdad es que no hay forma de evitar fácilmente el enredo de código en C++. Sin embargo, el uso del espacio de nombres global es la peor idea, porque entonces no hay forma de elegir entre implementaciones. Terminas con Object en lugar de Object. Esto funciona bien en casa porque puede editar la fuente sobre la marcha, pero si alguien envía un código así a un cliente, no deberían esperar que sea por mucho tiempo.

Una vez que use la instrucción using, también puede estar en global, pero puede ser útil en los archivos cpp para usarla.Así que diría que debería tener todo en un espacio de nombres, pero para el interior debería ser el mismo espacio de nombres. De esa manera otras personas pueden usar su código aún sin un desastre.

2

me gustaría ampliar en el segundo párrafo de David Rodríguez - respuesta dribeas' (upvoted):

para facilitar la expedición que podría haber escrito una cabecera hacia adelante (hay un par en el STL , seguramente puede encontrarlo en las bibliotecas) como usefulfwd.h que solo define la interfaz de la biblioteca (o implementando clases o lo que sea que necesite). Pero esto no tiene nada que ver con el acoplamiento.

Creo que esto apunta al núcleo de su problema. Los espacios de nombres son una gran amenaza, te picaron al subestimar la necesidad de contener dependencias sintácticas.

Puedo entender su "pereza": que es no derecho a overengineer (empresa HelloWorld.java), pero si se mantiene el código de bajo perfil en el inicio (que no es necesariamente malo) y la el código resulta exitoso, el éxito lo arrastrará por encima de su liga. el truco es detectar el momento adecuado para cambiar (o emplear desde el primer momento en que aparece la necesidad) una técnica que raspa su picazón de una manera compatible con el futuro.

Las declaraciones adelante espumoso sobre un proyecto solo mendiga una segunda ronda y las subsiguientes. Realmente no necesita ser un programador de C++ para haber leído el consejo "no reenviar-declarar transmisiones estándar, use <iosfwd> en su lugar" (aunque han pasado algunos años cuando esto era relevante; 1999, era VC6, definitivamente). Puede escuchar muchos gritos dolorosos de los programadores que no prestaron atención al consejo si se detienen un poco.

I puede entender la necesidad de mantenerlo bajo la frente, pero hay que admitir que #include <usefulfwd.h> hay más dolor que class Useful y escalas. Esta simple delegación le ahorraría N-1 cambios de class Useful a class useful::Useful.

Por supuesto, no le ayudaría con todos los usos en el código del cliente. Ayuda fácil: de hecho, si usa una biblioteca en una aplicación grande, debe envolver los encabezados directos suministrados con la biblioteca en encabezados específicos de la aplicación. La importancia de esto crece con el alcance de la dependencia y la volatilidad de la biblioteca.

src/libuseful/usefulfwd.h

#ifndef GUARD 
#define GUARD 
namespace useful { 
    class Useful; 
} // namespace useful 
#endif 

src/miaplicacion/miaplicacion-usefulfwd.h

#ifndef GUARD 
#define GUARD 
#include <usefulfwd.h> 
using useful::Useful; 
#endif 

Básicamente, es una cuestión de mantener el código SECO . Puede que no te gusten los TLA pegadizos, pero este describe un principio de programación verdaderamente central.

Cuestiones relacionadas