访问c++类的私有成员
访问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
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
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的实际访问地址后,可以对变量进行随意读写