訪問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的實際訪問地址后,可以對變量進行隨意讀寫