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

 

posted on 2021-11-17 14:28  炽离  阅读(1194)  评论(0编辑  收藏  举报

导航