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.
Eeewww! ¡Yucky! ¡Blech! –