2009-12-14 29 views
5

bien, esta cuestión ha evolucionado un poco, y quiero tratar de empezar (más) con los objetivos básicos estoy tirando para:C++ Espacio de nombres dolores de cabeza

  • Crear código de biblioteca que envolturas legado de lenguaje C las entidades en la adquisición de recursos de C++ son la inicialización y también proporcionan una garantía de excepción básica o mejor.
  • Habilite a los clientes de este código para usarlo de forma C++ muy natural sin crear una sobrecarga al código existente para convertirlo en objetos de envoltura C++ (es decir, conversión automática a tipos heredados apropiados, constructores heredados tipos, etc.)
  • Limite el impacto en el espacio de nombres del código de la biblioteca. Idealmente, la biblioteca tendría varios espacios de nombres que proporcionan funcionalidades relacionadas que limitan el volumen y el impacto del uso de declaraciones de tipo X de espacio de nombres, al igual que las bibliotecas de refuerzo (es decir, el uso de espacios de nombres de detalles para inyectar únicamente los símbolos que el usuario razonablemente desea utilizar y ocultar aquellos que son detalles de implementación; también limitar la extensión de posibles significados nuevos a símbolos existentes, para evitar conversiones implícitas sorprendentes dentro del código de usuario)
  • Requerir que los clientes soliciten explícitamente aquellas partes de la biblioteca que realmente querer inyectar en su base de código. Esto va de la mano con la limitación del impacto de la inclusión de los encabezados de la biblioteca. El código del cliente debe tener un nivel razonable de control sobre qué partes de la biblioteca se usarán automáticamente para la resolución del nombre cuando compilan su código.
  • Mi propio código de biblioteca no debería tener que estar plagado de construcciones de código frágil de refactor. Sería ideal si los encabezados de la biblioteca no tuvieran que declarar constantemente typedefs privados para tener acceso al resto de esa sección de la biblioteca. O en otras palabras: quiero que mi biblioteca pueda escribirse de manera tan intuitiva como mis clientes cuando utilizan dicha biblioteca. La resolución del nombre debe incluir el espacio de nombre en el que se define la biblioteca, además de cualquier otro que haya sido explícitamente "usado".

me encuentro con este escenario menudo, y estoy buscando una mejor manera ...

tengo una clase, C en espacio de nombres N. C tiene un miembro, gratuito. Libera algo que C maneja, y permite que C administre algo nuevo.

Existen varias funciones globales gratuitas. También hay algunas funciones auxiliares en el mismo espacio de nombres N como C, una de las cuales es una ayuda que libera la cosa administrada por C, denominada libre.

Así que tenemos algo así como:

namespace N { 

void free(THING * thing); 

class C 
{ 
public: 
    ... details omitted... 
    free() 
    { 
    free(m_thing); // <- how best to refer to N::free(THING&) 
    } 
} 

} // namespace N 

que podría utilizar N :: libre (m_thing). Pero eso me parece desafortunado. ¿No hay forma de referirse a lo que está fuera del alcance de la clase, pero sin resolver el espacio de nombres absoluto (un relativo paso a paso del alcance)?

Me parece que tener que nombrar N :: free es desagradable, ya que no tendrías que hacerlo si se tratara de una función independiente. Tampoco lo necesitaría si el nombre del método de la clase fuera diferente (por ejemplo, disponer). Pero debido a que he usado el mismo nombre, no puedo acceder a él sin tener que especificar lo que equivale a una ruta absoluta, en lugar de una ruta relativa, si me permite la analogía.

Odio las rutas absolutas. Hacen que las cosas en movimiento en los espacios de nombres sean muy frágiles, por lo que la refactorización de códigos se vuelve mucho más fea. Además, las reglas de cómo nombrar las cosas en los cuerpos funcionales se vuelven más complejas con el conjunto de reglas actual (como yo las entiendo) - menos regular - induciendo un cisma entre lo que uno espera y lo que uno obtiene como programador.

¿Hay alguna manera mejor de acceder a las funciones autónomas en el mismo espacio de nombres que una clase sin tener que nombrar absolutamente absolutamente la función libre?

EDIT: Tal vez debería haber ido con un ejemplo menos abstracto:

namespace Toolbox { 
    namespace Windows { 

// deallocates the given PIDL 
void Free(ITEMIDLIST ** ppidl); 

class Pidl 
{ 
public: 
    // create empty 
    Pidl() : m_pidl(NULL) { } 

    // create a copy of a given PIDL 
    explicit Pidl(const ITEMIDLIST * pidl); 

    // create a PIDL from an IShellFolder 
    explicit Pidl(IShellFolder * folder); 

    ... 

    // dispose of the underlying ITEMIDLIST* so we can be free to manage another... 
    void Free(); 
}; 

Así ITEMIDLIST * provienen de una variedad de lugares, y se destruyen con CoTaskMemFree(). Podría presentar a Pidl como un nombre global, así como a todas las funciones de ayuda en el encabezado "Windows Shell.h" que es parte de mi biblioteca de caja de herramientas.

Idealmente, segmentaría algunas de las herramientas en mi biblioteca por lo que se refieren a ellas, en este caso todo lo anterior se relaciona con la programación COM en Windows. He elegido Toolbox como el espacio de nombres base para mis cosas de bibliotecas, y estaba pensando que usaría Toolbox :: Windows para funciones, clases, etc. de Windows-y.

Pero las reglas de espacio de nombre y resolución de nombres C++ parecen para hacer esto muy difícil (de ahí esta pregunta). Resulta muy antinatural crear dicha segmentación de mi código, ya que falla la búsqueda koenig (ya que ITEMIDLIST no está en mi Toolbox :: espacio de nombres de Windows), ¡y no tengo la capacidad de moverlo allí! Tampoco debería I. El lenguaje debe ser lo suficientemente flexible, IMO, para permitir bibliotecas de extensión como mi biblioteca Toolbox para extender el código de otras personas sin tener que inyectar mis extensiones en su espacio de nombres (que, en el caso de Win32 y el vasto general La mayoría del código que existe hoy en día es GLOBAL NS, que es el objetivo principal de crear espacios de nombres en primer lugar: para evitar la saturación/contaminación/ambigüedad/sorpresas de programación globales de NS.

Por lo tanto, vuelvo a, ¿Hay una mejor manera de hacer esto: ampliar las bibliotecas existentes de código sin contaminar NS con mis extensiones pero aún así permitir una resolución de nombre intuitiva y útil como uno esperaría si mi El código estaba en su NS pero explícitamente introducido por el cliente de mi código (es decir, no quiero inyectar mi código de cualquier manera, pero solo a pedido explícito).

otro pensamiento: Quizá lo que satisfaría mi introduciendo criterios más arriba sería si tuviera el siguiente:

using namespace X { 
    code here... 
} 

donde podría colocar una construcción de este tipo en cualquier lugar, incluso en una cabecera, y yo no tendría que Preocuparse por arrastrar X al código de mi cliente, pero tendría la misma libertad para escribir mi código fuente como lo haría si estuviera en el espacio de nombres raíz.

+5

Para jugar al abogado del diablo. N: free() significaría más para mí si comencé a leer tu código fuente en una ubicación aleatoria. Especialmente si vi X: libre usé un par de líneas después.Me facilita leer su código sin sus comentarios/documentación –

+0

Para mí, eso parece contraproducente. En ese caso, uno debería simplemente poner todos los nombres en el espacio de nombres global, y asegurarse de que sean realmente explícitos: "free_a_thing()," create_a_thing() "etc., ya que eso parece lo que sucede cuando tiene que poner el De todos modos, la idea general de las funciones de sobrecarga es que el compilador resuelva el correcto en función del uso, una inferencia inteligente de lo que uno escribe. Tree t; Bear b; Can c; free (t) ; libre (b); libre (c); ¿Por qué es peor que free_a_tree (t), free_a_can (c), ... ???! – Mordachai

+1

¿Por qué no 'borrar m_thing'? – GManNickG

Respuesta

6

No quiero evitar tener que calificar el espacio de nombres-scope free() aquí, pero debe tenerse en cuenta que esto no es lo mismo que una "ruta absoluta". Por un lado, si ha anidado espacios de nombres, es suficiente para referirse a la más interna:

namespace N1 { 
    namespace N2 { 
    namespace N3 { 
     void free(THING * thing); 

     class C { 
     public: 
     free() { 
      N3::free(m_Thing); // no need to do N1::N2:: 
     } 
     }; 
    } 
    } 
} 

[EDIT] en respuesta a la pregunta editado. De nuevo, no veo ninguna manera de hacer esto en el escenario exacto que describes. Sin embargo, no parece ser un enfoque idiomático de C++: la forma más común de hacer lo mismo es proporcionar su propia clase contenedora para ITEMIDLIST que gestiona todas las asignaciones, estilo RAII, y expone el identificador original (por ejemplo, a través de operadores de conversión la ATL, o una función explícita de miembro como c_str() si desea seguridad adicional). Eso eliminaría la necesidad de su free en total, y para cualquier otra función gratuita que desee, ya que controla el tipo de contenedor y el espacio de nombres en el que se encuentra, puede usar ADL como de costumbre.

[EDIT # 2]. Esta parte de la pregunta:

permiten una resolución de nombre intuitiva y útil como uno esperaría si mi código estuviera en su NS pero explícitamente introducido por el cliente de mi código (es decir, no quiero inyectar mi código quiera o no, pero solo previa solicitud explícita)?

¿No es esto exactamente lo que pone en un espacio de nombre, y su cliente escribiendo using namespace ..., logrará?

+0

¡Woot! Aprendo algo nuevo cada vez que hago preguntas aquí. Gracias Pavel.Si aún no lo ha hecho, ¿podría echar un vistazo a mi pregunta actualizada y ver si tiene alguna idea sobre el problema más grande en cuestión (introducir selectivamente funciones libres polimórficas en el código del cliente y la posibilidad de hacerlo para mis propias clases, incluso en mis encabezados, sin código de cliente contaminante) – Mordachai

+0

He actualizado la respuesta. –

+0

Gracias de nuevo. Y en cuanto a editar # 2 - sí, eso es lo que quiero. Sé que poner mi código en su propio NS logra esto. Pero el inconveniente de usar mi propio NS es que falla la búsqueda de koenig (¿es eso lo que es ADL?) Para la búsqueda de miembro de clase a función libre, según mi pregunta original. Entonces, ¿cómo proporcionar las ventajas de forzarlos a incluir explícitamente mi NS, sin tener los inconvenientes de dolores de cabeza de resolución de nombres en mi código? – Mordachai

5

Usted tiene dos opciones:

  1. calificar totalmente nombre de la función.
  2. Deje que Koenig lookup haga el trabajo (si THING pertenece al espacio de nombres N).

Elegiría el primero con el nombre de función popular como free.

La opción alternativa es usar m_thing->Release() o algo así.

+0

Me preguntaba si Koenig Lookup realmente haría el trabajo, ¿no corre el riesgo de que 'MyClass :: free' oculte' N :: free' durante la búsqueda? –

+0

Solo si 'MyClass :: free' tiene la misma cantidad de argumentos que' N :: free'. En ese caso, no se ocultará, la llamada será ambigua y recibirá un mensaje de error. –

+0

La búsqueda Koenig es exactamente lo que quiero. Desafortunadamente, como suele ser el caso para mí, THING NO está definido en el espacio de nombres N. Está en el espacio de nombres global, provisto con mayor frecuencia por Microsoft (pero a veces otro proveedor de la biblioteca), y porque es una C-struct o calvo puntero, estoy tratando de crear una clase contenedora para administrar la vida útil del objeto subyacente. Así que parece que puedo crear un free (ITEMIDLIST *) por ejemplo, pero solo si lo ubico en el espacio de nombres global será encontrado. Y como MattM indica, incluso entonces estará oculto por el miembro de la clase: Tengo que usar :: free (cosa) :( – Mordachai

0

Yuu podría escribir ::free(m_thing);. Esto llamará a la función global free(). Sin embargo, si tiene otra función free() fuera del espacio de nombres N, se ha metido en problemas.Cambie los nombres de algunas de sus funciones o use el nombre de la función con espacio de nombres explícito.

+0

-1 free está en el espacio de nombres N. Tengo dudas en poner en libertad (COSA) en el NS global para evitar inyectar polimorfismo de función libre en el código de mi cliente sin algún nivel de control (por ejemplo, tener que forzarlos a decir "usando el espacio de nombres XYX" o "usando XYZ :: free". En este punto, sé que realmente quieren gratis (THIN G) para ser inyectado en su unidad de compilación, y es razonablemente probable que no se sorprendan. – Mordachai

0

Utilizaría un enfoque completamente diferente de lo que está tratando de lograr. Creo que necesitas un poco de rediseño. Elimina los globales y crea una fábrica/fábrica abstracta que se fabricará (crea una instancia de tu clase) y destrúyela con el destructor. De hecho, ese sería el modismo de RAII.

+0

Esa es una buena expresión idiomática. Pero al igual que el código MFC, estoy tratando de ampliar el entorno para permitir la interacción entre el SO crudo definido como THING y una clase inteligente de ayuda C (en mi ejemplo). THING ya ha sido creado de varias maneras por el sistema operativo. Y debe ser destruido usando una mecánica definida por el sistema operativo. Solo quiero una clase de contenedor que oculte estos detalles y haga que el uso de THING sea natural y muy poco probable que escape. – Mordachai

+0

¿Qué es THING exactamente? – Partial

+0

He extendido mi pregunta para aclarar con suerte lo que estoy tratando de lograr, y también con un segundo ejemplo más concreto (THING es ITEMIDLIST *) – Mordachai

0

Me parece que hay un problema de diseño aquí.

Me parece extraño tener el mismo identificador utilizado como método de función libre y clase, realmente es confuso. Trato de minimizar la ocurrencia tanto como sea posible, aunque reconozco que sucede por swap ...

no tratar de calificar los nombres dentro del método, pero aquí, por supuesto, se corre el riesgo de hiding, donde MyClass::free oculta el método N::free, que nunca participa en la búsqueda ... trató de encontrar la resolución del alcance aquí pero no pudo encontrar el pasaje exacto.

Para permuta simplemente uso:

class MyClass 
{ 
    void swap(MyClass& rhs) 
    { 
    using std::swap;   // Because int, etc... are not in std 
    swap(m_foo, rhs.m_foo); 
    swap(m_bar, rhs.m_bar); 
    } 
}; 

inline void swap(MyClass& lhs, MyClass& rhs) { lhs.swap(rhs); } 

Por lo tanto asegúrese de que el método adecuado se incluirán en la búsqueda, y evitar caer de nuevo en un método 'global' si los hubiere.

Prefiero este enfoque a la nomenclatura explícita y usualmente los pernos using ydeclaración en la parte superior del método para que el código real no esté inflado.

+0

Sí, el problema es que THING es realmente una entidad de lenguaje C, y no realmente uno de C++. Estoy tratando de proporcionar un contenedor para la cruda C-thing y hacer que mi envoltorio funcione con las reglas de C++ para permitir una gran facilidad de uso sin introducir sorpresas para mis clientes, y sin tener que escribir código frágil en mis encabezados. – Mordachai

+0

Envolviendo C en C++ es a menudo complicado, sin embargo, es muy encomiable por lo que te deseo mucha suerte. ¡Ojalá todos los demás desarrolladores se preocupen tanto por sus clientes! –

Cuestiones relacionadas