Today I learned about the C++ "memberspace" idiom, which roughly abuses a property of C++ that makes T::bar
as well as T.bar
work, when T
is both a type and an object in some scope.
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();
Have you ever used this idiom in practice? Have you found it useful? What's some good or the best application of the idiom?
No, I have never used that technique (and I don't think it deserves to be called an "idiom"):
Since I haven't used it, I haven't found it useful.
A good application of that technique could be to confuse other programmers.
Another application could be to write a techno-babble article about how wonderful it is for some imagined never-in-practice encountered problem, perhaps obfuscated with lots of template metaprogramming?
Dunno, best application would probably be to write an article about all those silly-rules, like you can also have a struct
and a function of the same name in the same scope, as I recall, and point out how anything that those can accomplish, can be accomplished much better by staying away from the darker corners of the language. :-) Articles don't pay much in moneys but they pay in respect and are fun to write. Please write it (TIA).
Cheers & hth.,
As @Steve already said in a comment, the fact that the nested type and the instance of this type have the same name is not a central aspect of this technique. To increase encapsulation, we could even use a member function to access the instance (even though it would less look like a namespace qualification). For example, the example you gave could be rewritten as follow without any drawback (well, maybe there are, but I can't find any at the moment):
struct A
{
struct Controls
{
//typedefs/data/functions
};
const Controls & getControls() { return controls_; }
private:
Controls controls_;
};
A a;
A::Controls::iterator = a.getControls().begin();
Here is how I see memberspaces. The goal of memberspaces is to divide the naming space of a class in order to group together related typedefs and methods. The Controls
class above could be defined outside of A
, but it is so tightly connected to A
(each A
is associated with a Controls
, and vice-versa, a Controls
is nothing more than a view on the object A
in which it is contained) that it feels "natural" to make it a member of A
, and possibly also make it friend with A
(if there is a need to access A
's internals).
So basically memberspaces let us define one or several views on a single object, without polluting the enclosing class namespace. As noted in the article, this can be quite interesting when you want to provide several ways to iterate over an object of your class.
For example, let's assume that I am writing a class for representing C++ classes; let's call it Class. A Class has a name, and the list of all its base classes. For convenience reasons, I would like a Class to also store the list of all the classes that inherit from it (its derived classes). So I would have a code like that:
class Class
{
string name_;
list< shared_ptr< Class > > baseClasses_;
list< shared_ptr< Class > > derivedClasses_;
};
Now, I need some member functions to add/remove base classes/derived classes:
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
};
And sometimes, I might need to add a way to iterate over base classes and derived classes:
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
};
The amount of names we are dealing with is still manageable, but what if we want to add reverse iteration? What if we change the underlying type for storing derived classes? That would add another bunch of typedefs. Moreover, you have probably noticed that the way we provide access to begin and end iterators does not follow the standard naming, which means we can't use generic algorithms relying on it (such as Boost.Range) without additional effort.
In fact, it is obvious when looking at the member functions name that we used a prefix/suffix to logically group them, things that we try to avoid now that we have namespaces. But since we can't have namespaces in classes, we need to use a trick.
Now, using memberspaces, we encapsulate all base-related and derived-related information in their own class, which not only let us group together related data/operations, but can also reduce code duplication: since the code for manipulating base classes and derived classes is the same, we can even use a single nested class:
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
};
Now I can do:
Class c;
Class::DerivedClasses::const_iterator = c.derivedClasses.begin();
boost::algorithm::find( c.derivedClasses, & c );
...
In this example, the nested class is not so coupled to Class
, so it could be defined outside, but you could find examples with a stronger bound.
Well, after this long post, I notice that I did not really answer your question :). So no, I never actually used memberspaces in my code, but I think it has its applications.
I have considered it once or twice, notably when I was writing a facade class for a library: the facade was meant to make the library easier to use by having a single entry point, but as a consequence it had several member functions, which were all related, but with different degrees of "relatedness". Moreover, it represented a collection of objects, so it contained iteration-related typedefs and member functions in addition to "features-oriented" member functions. I considered using memberspaces to divide the class in logical "subspaces" in order to have a cleaner interface. Don't know why I haven't done it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With