2011-03-11 7 views
9

Hoy aprendí sobre el modismo de C++ "espacio de miembros", que abusa de una propiedad de C++ que hace que T::bar y T.bar funcionen, cuando T es un tipo y un objeto en algún ámbito.Uso de la expresión idiomática "espacio de miembros"?

struct A { 
    struct Controls { 
    /* put some typedefs/data/functions here */ 
    } Controls; 
}; 

// Can be used as a type and value 
A a; 
A::Controls::iterator it = a.Controls.begin(); 

¿Alguna vez ha usado este idioma en la práctica? ¿Lo has encontrado útil? ¿Cuál es una buena o la mejor aplicación de la expresión idiomática?

+3

Eeewww! ¡Yucky! ¡Blech! –

Respuesta

6

No, nunca han utilizado esta técnica (y no creo que merece ser llamado un "lenguaje"):

Ya que no he utilizado, no he encontrado útil.

Una buena aplicación de esa técnica podría ser confundir a otros programadores.

Otra aplicación podría ser escribir un artículo de techno-babble sobre lo maravilloso que es para un problema imaginado que nunca se ha encontrado en la práctica, tal vez ofuscado con una gran cantidad de metaprogramación de plantillas.

Dunno, la mejor aplicación sería probablemente escribir un artículo sobre todas esas reglas tontas, como también puede tener una función del mismo nombre en el mismo ámbito, como recuerdo, y señalar cómo cualquier cosa que esos pueden lograr, se puede lograr mucho mejor al mantenerse alejado de los rincones más oscuros del idioma. :-) Los artículos no pagan mucho en dinero, pero pagan con respeto y son divertidos de escribir. Por favor escríbalo (TIA).

Saludos & HTH.,

+2

El artículo sobre esto está en http://accu.org/index.php/journals/1527. Mi colega me contó sobre este modismo/"modismo". –

+1

@litb: "Esto es legal y aunque no cumple un propósito explícito, me parece una manera fácil de reconocer los espacios de miembros en el código" - dijo nuff. Aquellos que prefieren una forma fácil y fácil de distinguir los nombres de clase de los nombres de objeto no tienen que usarlo :-) No creo que esta convención de nomenclatura sea vital para el modismo descrito en el artículo. La expresión idiomática parece bastante sensata, en los ejemplos simples solo está exponiendo la composición del objeto en su interfaz pública. –

+0

Ah, y lo de las técnicas intrusivas y no intrusivas de mantener una referencia al objeto que lo contiene es una manera tortuosa de reproducir una característica de las clases internas en Java. Para el uso dudoso de 'offsetof', me inclino por lo que dice Alf, probablemente sea más útil como material de forraje que en la práctica si no quieres que tus colegas te persigan y te maten ... –

1

No, nunca he usado eso.

¿Un buen uso para ello? Tal vez puedas usarlo para mostrar a tus colegas que eres mejor de lo que son ... como algunas personas usan plantillas donde no deberían, para una solución compleja a un problema simple (ten en cuenta que las plantillas, a diferencia del lenguaje del miembro, a veces son útiles)

+3

¿Algunas veces es útil? ¿Qué tal extremadamente útil y a veces inapropiado? – GManNickG

+1

Sí, a menudo es extremadamente útil, pero es una de las características C++ que utilizan los programadores inexpertos. – fbafelipe

+0

Cuidar para elaborar cómo es útil? –

2

Como @Steve ya dijo en un comentario, el hecho de que el tipo anidado y la instancia de este tipo tengan el mismo nombre no es un aspecto central de esta técnica. Para aumentar la encapsulación, incluso podríamos usar una función de miembro para acceder a la instancia (aunque se parecería menos a una calificación de espacio de nombres). Por ejemplo, el ejemplo que diste podría reescribirse de la siguiente sin ningún inconveniente (bueno, tal vez existen, pero no puedo encontrar ninguna por el momento):

struct A 
{ 
    struct Controls 
    { 
     //typedefs/data/functions 
    }; 

    const Controls & getControls() { return controls_; } 

    private: 

    Controls controls_; 
}; 

A a; 
A::Controls::iterator = a.getControls().begin(); 

Aquí es cómo veo memberspaces. El objetivo de los espacios de miembros es dividir el espacio de denominación de una clase para agrupar los métodos y tipos de letra relacionados. La clase anterior Controls podría definirse fuera de A, pero está tan estrechamente conectada a A (cada A está asociada a un Controls, y viceversa, un Controls no es más que una vista del objeto A en el que está contenido) que se siente "natural" convertirlo en miembro de A, y posiblemente también hacerlo amigo con A (si es necesario acceder al interior de A).

Así que, básicamente, los espacios de miembros nos permiten definir una o varias vistas en un solo objeto, sin contaminar el espacio de nombres de la clase adjunta. Como se señala en el artículo, esto puede ser bastante interesante cuando desea proporcionar varias formas de iterar sobre un objeto de su clase.

Por ejemplo, supongamos que estoy escribiendo una clase para representar clases de C++; llamémoslo Clase. Una clase tiene un nombre y la lista de todas sus clases base. Por razones de conveniencia, me gustaría que una clase también almacene la lista de todas las clases que heredan de ella (sus clases derivadas). Así que me gustaría tener un código de esa manera:

class Class 
{ 
    string name_; 
    list< shared_ptr<Class> > baseClasses_; 
    list< shared_ptr<Class> > derivedClasses_; 
}; 

Ahora, necesito algunas funciones miembro para añadir/quitar clases base/clases derivadas:

class Class 
{ 
    public: 

    void addBaseClass(shared_ptr<Class> base); 
    void removeBaseClass(shared_ptr<Class> base); 

    void addDerivedClass(shared_ptr<Class> derived); 
    void removeDerivedClass(shared_ptr<Class> derived); 

    private: 

    //... same as before 
}; 

Y a veces, puede ser que tenga que añadir una manera para repetir las clases base y clases derivadas:

class Class 
{ 
    public: 

    typedef list< shared_ptr<Class> >::const_iterator const_iterator; 

    const_iterator baseClassesBegin() const; 
    const_iterator baseClassesEnd() const; 

    const_iterator derivedClassesBegin() const; 
    const_iterator derivedClassesEnd() const; 

    //...same as before 
}; 

La cantidad de nombres que estamos tratando es todavía manejable, pero lo que si queremos añadir iteración inversa? ¿Qué ocurre si cambiamos el tipo subyacente para almacenar clases derivadas? Eso agregaría otro grupo de typedefs. Además, probablemente hayas notado que la forma en que brindamos acceso a los iteradores de inicio y fin no sigue el nombre estándar, lo que significa que no podemos usar algoritmos genéricos confiando en él (como Boost.Range) sin esfuerzo adicional.

De hecho, cuando se mira el nombre de funciones del miembro, es obvio que usamos un prefijo/sufijo para agruparlos lógicamente, cosas que tratamos de evitar ahora que tenemos espacios de nombres. Pero como no podemos tener espacios de nombres en las clases, tenemos que usar un truco.

Ahora, utilizando espacios de miembros, encapsulamos toda la información relacionada con bases y derivadas en su propia clase, lo que no solo nos permite agrupar datos/operaciones relacionados, sino también reducir la duplicación de código: desde el código para manipular base clases y las clases derivadas es el mismo, incluso podemos utilizar una sola clase anidada:

class Class 
{ 
    struct ClassesContainer 
    { 
     typedef list< shared_ptr<Class> > const_iterator; 

     ClassesContainer(list< shared_ptr<Class> > & classes) 
      : classes_(classes) 
     {} 

     const_iterator begin() const { return classes_.begin(); } 
     const_iterator end() const { return classes_.end(); } 

     void add(shared_ptr<Class> someClass) { classes_.push_back(someClass); } 
     void remove(shared_ptr<Class> someClass) { classes.erase(someClass); } 

     private: 

     list< shared_ptr<Class> > & classes_; 
    }; 

    public: 

    typedef ClassesContainer BaseClasses; 
    typedef ClassesContainer DerivedClasses; 

    // public member for simplicity; could be accessible through a function 
    BaseClasses baseClasses; // constructed with baseClasses_ 
    DerivedClasses derivedClasses; // constructed with derivedClasses_ 

    // ... same as before 
}; 

Ahora puede hacer:

Class c; 
Class::DerivedClasses::const_iterator = c.derivedClasses.begin(); 

boost::algorithm::find(c.derivedClasses, & c); 
... 

En este ejemplo, la clase anidada no es tan acoplado a Class, por lo que podría definirse o Utside, pero podrías encontrar ejemplos con un límite más fuerte.


Bueno, después de esta larga publicación, noté que realmente no respondí tu pregunta :). Así que no, nunca usé espacios de miembros en mi código, pero creo que tiene sus aplicaciones.

Lo he considerado una o dos veces, especialmente cuando escribía una clase de fachada para una biblioteca: la fachada estaba destinada a hacer que la biblioteca fuera más fácil de usar al tener un único punto de entrada, pero como consecuencia tenía varios miembros funciones, que estaban todas relacionadas, pero con diferentes grados de "relación". Además, representaba una colección de objetos, por lo que contenía defi niciones de tipos y funciones de miembros relacionadas con la iteración, además de las funciones de miembros "orientadas a funciones". Consideré usar espacios de miembros para dividir la clase en "subespacios" lógicos para tener una interfaz más limpia. No sé por qué no lo he hecho.

+0

Descargo de responsabilidad: el código escrito en esta respuesta fue "improvisado", por lo que probablemente contenga una enorme cantidad de errores, errores y partes que podrían mejorarse. –

1

Cualquier flexibilidad de sintaxis siempre es bienvenida, independientemente de si es potencialmente confuso o no. De hecho el truco se utiliza en boost.array, esta es una implementación mínima:

#include<cassert> 

template<unsigned N> 
struct fixed_array{ /* bla bla */ 
    static unsigned size(){ 
     return N; 
    } 
}; 


int main(){ 
    fixed_array<3> arr; 
    assert(arr.size() == 3);    //like a stl container 
    assert(fixed_array<3>::size() == 3); //more proper, but less generic wrt stl containers 
    return 0; 
} 

por lo que si el usuario quiere ver una variable// clase anidada miembro estático función miembro estática como una propiedad de la instancia y no se la clase entonces él/ella puede.Es útil para escribir código genérico, por ejemplo, y no es nada confuso en este ejemplo.

Cuestiones relacionadas