Header only, fully template based library which enables accessing private data members. Techniques used by this library to achieve its purpose are fully legal and allowed by the standard. The library is based on Explicit instantiation of class template instantion.
Explicit instantiation definitions ignore member access specifiers: parameter types and return types may be private.
source: https://en.cppreference.com/w/cpp/language/class_template
The article describing implementation of this technique can be find Here
First reference to this technique is in Johannes Schaub - litb blog
Herb Sutter GotW blog post why in general you should not access private members. Be wise and don't try to break things if you don't have to!
This library is not meant to break any C++ design rules, even though it's fully legal from standard point of view. The purpose of this library is to use it when necessary within tests. Code shall be written in the way that it can be later tested, but as we all know in most cases and it especially applies to legacy code, there are situations where one need to access private member and no mocking can be applied anymore.
In situations like that provided technique is far better than common #define private public
, using friend
keyword or even flag no-access-control
for gcc.
Installation is done using CMake
mkdir build
cd build
cmake ..
make && make install
Installed Accessor library can be easily imported by any CMake based project
find_package(accessor REQUIRED)
# ...
target_link_libraries(${exec_name} accessor)
There is set of mini examples, which shows how various data members and methods can be accessed. To build them, the additional CMake flag shall be passed.
cmake -DEXAMPLES=1 ..
make
There is set of tests using only CMake CTest.
make test
Full set of examples is avaiable here. Below there is only quick getting started section
There are two ways of using this library, first is using straight library API and the second is by calling Macro which calls library API. Both ways will be presented, choose which one is better for you, yet both will use the same test class to access its data.
class Test
{
void foo() { std::cout << "private method: Foo" << '\n'; }
int getSum(int first, int second ) const { return first + second; }
template<typename T>
T max(T& lhs, T& rhs) { return (lhs > rhs) ? lhs : rhs; }
int mFooBar {1};
};
The first step for accessing private data, is to create own type or an alias which will be further passed for accessing functions.
struct TestFoo : ::accessor::FunctionWrapper<Test, void> {};
struct TestFooBar : ::accessor::MemberWrapper<Test, int> {};
Or
using TestFoo = ::accessor::FunctionWrapper<Test, void>;
using TestFooBar = ::accessor::MemberWrapper<Test, int>;
Accessing multiple members with the same type is only possible with the definition of a struct.
FunctionWrapper
takes types <BaseClass, FunctionReturnType, FunctionArgsTypes...>
MemberWrapper
works similar to FunctionWrapper
it takes types <BaseClass, MemberType>
Then explicit template instantantion of created type TestFoo
and TestFooBar
shall be used
using TestFoo = ::accessor::FunctionWrapper<Test, void>;
template class ::accessor::MakeProxy<TestFoo, &Test::foo>;
using TestFooBar = ::accessor::MemberWrapper<Test, int>;
template class ::accessor::MakeProxy<TestFooBar, &Test::mFooBar>;
MakeProxy
takes as parameters <CreatedType, MemberAddress>
After those steps we can simply call private foo
method of class Test
Test t;
::accessor::callFunction<TestFoo>(t);
auto ref = ::accessor::accessMember<TestFooBar>(t); \\ return std::ref(Test::mFooBar)
callFunction
takes any Test
object which has Test::foo
method to call, and variadic arguments needed for that method.
accessMember
works as callFunction
yet it return std::ref
to private data member of class Test
Examples of instantion and calling for rest of Test class methods.
using TestGetSum = ::accessor::ConstFunctionWrapper<Test, int, int, int>;
template class ::accessor::MakeProxy<TestGetSum, &Test::getSum>;
template<typename T>
using TestMax = ::accessor::FunctionWrapper<Test, T, T&, T&>;
template class ::accessor::MakeProxy<TestMax<int>, &Test::max>;
template class ::accessor::MakeProxy<TestMax<uint32_t>, &Test::max>;
....
int main()
{
int result = ::accessor::callFunction<TestGetSum>(t, 1, 1);
result = ::accessor::callFunction<TestMax<int>>(t, 10, 20);
uint32_t res = ::accessor::callFunction<TestMax<uint32_t>>(t, 100u, 200u);
return 0;
}
If you are not happy with the need of explicitly calling template instantiation, then there are also macros which does it for you
FUNCTION_ACCESSOR(TestFoo, Test, foo, void) // Test::foo
CONST_FUNCTION_ACCESSOR(TestSum, Test, getSum, int, int, int) //Test::getSum
FUNCTION_ACCESSOR(TestMaxInt, Test, max, int, int&, int&) //Test::max<int>
FUNCTION_ACCESSOR(TestMaxUInt, Test, max, uint32_t, uint32_t&, uint32_t&) //Test::max<uint32_t>
MEMBER_ACCESSOR(TestFooBar, Test, mFooBar, int) // Test::mFooBar
Below is what those Macros shall be satisfied with
FUNCTION_ACCESSOR(TypeForAccessing, BaseClass, nameOfMemberToAccess, returnType, functionArgumentTypes...)
CONST_FUNCTION_ACCESSOR(TypeForAccessing, BaseClass, nameOfMemberToAccess, returnType, functionArgumentTypes...)
MEMBER_ACCESSOR(TypeForAccessing, BaseClass, nameOfMemberToAccess, MemberType)
The rest is done the very same way as it was done without macros, using callFunction
and accessMember
Full code
FUNCTION_ACCESSOR(TestFoo, Test, foo, void) // Test::foo
CONST_FUNCTION_ACCESSOR(TestSum, Test, getSum, int, int, int) //Test::getSum
FUNCTION_ACCESSOR(TestMaxInt, Test, max, int, int&, int&) //Test::max<int>
FUNCTION_ACCESSOR(TestMaxUInt, Test, max, uint32_t, uint32_t&, uint32_t&) //Test::max<uint32_t>
MEMBER_ACCESSOR(TestFooBar, Test, mFooBar, int) // Test::mFooBar
int main()
{
accessor::callFunction<TestFoo>(t); //Test::foo
int result = ::accessor::callFunction<TestGetSum>(t, 1, 1); //Test::getSum
result = ::accessor::callFunction<TestMax<int>>(t, 10, 20); //Test::max<int>
uint32_t res = ::accessor::callFunction<TestMax<uint32_t>>(t, 100u, 200u); //Test::max<uint32_t>
auto ref = accessor::accessMember<TestFooBar>(t); //Test::mFoobar
ref.get() = 200; //change mFooBar value;
return 0;
}