访问c++类的私有成员
1 目标
近期需要对代码进行单测覆盖,期望不改动代码仓库的情况下,单测有足够多的灵活度,直接根据历史覆盖行数,设计出全覆盖的单测。因此,访问类的私有成员变量和函数必不可少。然后,c++类本身设计为对外部访问封闭(friend class or function也是要改动代码,放弃),需要调研一下访问私有的一下trait
2 方案1
#define private public #define protected public
这两行代码,在编译预处理阶段,将private和protected关键字视为marco,替换为public。由于杀伤力大,仅能定义在cpp中,且依附在需要打开的h之前
3 方案2
c++标准仅在一种情况下支持,在类的外部访问私有成员变量(注意是类,而非类实例),那就是显示模板实例(explicit template instantiations)。
参考代码:二者原理差不多
https://github.com/martong/access_private
https://gist.github.com/dabrahams/1528856
下面是一些用法和分析:
1 #include "AccessPrivate2.h" 2 3 class A { 4 public: 5 A() {} 6 int GetA() const {return ia;} 7 protected: 8 int ib{0}; 9 private: 10 int ia {0}; 11 }; 12 13 ACCESS_PRIVATE_FIELD(A, int, ia); 14 15 int main() { 16 A a; 17 auto& ia = access_private::ia(a); 18 ia = 3; 19 return 0; 20 }
对代码进行预编译展开后,核心代码片段为:
1 # 8 "./AccessPrivate2.h" 2 2 # 21 "./AccessPrivate2.h" 3 namespace { 4 namespace private_access_detail { 5 template <typename PtrType, PtrType PtrValue, typename TagType> 6 struct private_access { 7 friend PtrType get(TagType) { return PtrValue; } 8 }; 9 } 10 } 11 # 4 "main.cpp" 2 12 13 14 class A { 15 public: 16 A() {} 17 int GetA() const {return ia;} 18 protected: 19 int ib{0}; 20 private: 21 int ia {0}; 22 }; 23 24 namespace { 25 namespace private_access_detail { 26 struct PrivateAccessTag0 {}; 27 template struct private_access<__decltype(&A::ia), &A::ia, PrivateAccessTag0>; 28 using Alias_PrivateAccessTag0 = int; 29 using PtrType_PrivateAccessTag0 = Alias_PrivateAccessTag0 A::*; 30 PtrType_PrivateAccessTag0 get(PrivateAccessTag0); 31 } 32 } 33 34 namespace { 35 namespace access_private { 36 int &ia(A &&t) { return t.*get(private_access_detail::PrivateAccessTag0{}); } 37 int &ia(A &t) { return t.*get(private_access_detail::PrivateAccessTag0{}); } 38 using XPrivateAccessTag0 = int; 39 using YPrivateAccessTag0 = const XPrivateAccessTag0; 40 YPrivateAccessTag0 & ia(const A &t) { 41 return t.*get(private_access_detail::PrivateAccessTag0{}); 42 } 43 } 44 }; 45 46 int main() { 47 A a; 48 auto& ia = access_private::ia(a); 49 ia = 3; 50 return 0; 51 }
再对名空间化简,得到最简可编译运行代码
1 //#include "Test.h" 2 //#include "AccessPrivate2.h" 3 4 class A { 5 public: 6 A() {} 7 int GetA() const {return ia;} 8 protected: 9 int ib{0}; 10 private: 11 int ia {0}; 12 }; 13 14 namespace access_private { 15 // step1: global template define。 16 template <typename PtrType, PtrType PtrValue> 17 struct private_access { 18 // friend function means it is not a part of this struct, but it can use template params, amazing 19 friend PtrType get() { return PtrValue; } 20 }; 21 22 // step2: explicit template instantiations : define by class name and member name 23 template struct private_access<__decltype(&A::ia), &A::ia>; 24 int A::* get(); 25 26 // step3: define function instance 27 int &ia(A &&t) { return t.* get(); } 28 int &ia(A &t) { return t.* get(); } 29 const int & ia(const A &t) { 30 return t.* get(); 31 } 32 } 33 //ACCESS_PRIVATE_FIELD(A, int, ia); 34 35 int main() { 36 A a; 37 auto& ia = access_private::ia(a); 38 ia = 3; 39 return 0; 40 }
可以看出来,替换分成3段,
第一段:全局的模版声明。这里面有个trait,在template struct里面仅定义了一个friend function,意味着这个函数非struct的一部分,但是可以享受到struct的模板参数,这个设计比较巧妙!
第二段:显示模板实例,c++里仅能访问类私有变量的地方。注意是class type的私有变量引用(偏移地址)
第三段:访问函数实现。参数传递class instantiation,get返回的class type的私有变量引用(偏移地址),映射到以class instantiation起始的地址上,获取实例变量的实际访问地址
拿到class instantiation的实际访问地址后,可以对变量进行随意读写