Have you ever wondered if there is a standard conformant way to access a private member in C++. One could #define private public
or try to reinterpret_cast
to a type with similar layout or violate the language in a similar way. So is there a better way to do it?
// library code template<typename type> struct access { inline static type member_pointer; }; template<typename type, type pointer> struct create_access : access<type> { struct assign_pointer { assign_pointer() { access<type>::member_pointer = pointer; } }; inline static assign_pointer by_calling_default_ctor; }; // application code #include <iostream> struct Alexander { private: void quote() { std::cout << "Performance constraints are good for creativity." << std::endl; } }; using type = void(Alexander::*)(); template struct create_access<type, &Alexander::quote>; int main() { Alexander S; (S.*access<type>::member_pointer)(); }
The above code calls the private function quote
of the struct S
in the last line of main and the application prints out the quote. But how does it work? The trick is that we use an indirection to call the private member. More specific we call a member function pointer on the object that was acquired at compile-time, thus avoiding the access checking.
(S.*access<type>::member_pointer)();
The above line does the call. (obj.*pmf)()
is the syntax for calling a member function on obj
through the pointer pmf
. Now we need to understand how to get hold of such a pointer. If quote was a public member we would manage with the following code:
using type = void(Alexander::*)(); type pmf = &Alexander::quote; Alexander S; (S.*pmf)();
But since the member is private we are going to fail with an error that says that 'void Alexander::quote()' is private within this context
. To avoid the error we initialize the pointer through an explicit template instantiation in our application code:
using type = void(Alexander::*)(); template struct create_access<type, &Alexander::quote>;
To understand what is going on we need to know that the instantiation will allocate objects with static storage duration at compile-time or dynamically pre main’s fist statement1 and that create_access
and access
make use of this fact. When the create_acess
template is instantiated first an instance of its base type access
is created that provides storage for a member pointer. Then its own member assign_pointer
will be default initialized. Its constructor assigns the member-pointer that is provided as a non-type template parameter to the storage of access.
template<typename type, type pointer> struct create_access : access<type> { struct assign_pointer { assign_pointer() { access<type>::member_pointer = pointer; } }; inline static assign_pointer by_calling_default_ctor; }; template<typename type> struct access { inline static type member_pointer; };
Now that access<type>::member_pointer
has been assigned the value of &S::quote
we are ready to call the function.
This post has been inspired by litb’s blogpost access to private members thats easy. I think that the use of inline variables fixes the static initialization fiasco that the original code has suffered from. That post in turn is based on Herb Sutter’s GotW #76.