0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

从技术实现的角度破坏封装性直接访问私有变量

CPP开发者 来源:高性能架构探索 2023-02-15 09:31 次阅读

C++教材的前几章就介绍了其特性,即:C++是一门面向对象语言,具有封装、继承和多态三个特点。后来,随着编码的增多以及工作经验的积累,对个概念的理解越来越深。编码习惯也严格按照相应的规则,该封装的时候进行封装,该继承的时候进行继承,以使得编程思维从之前的面向过程逐步过渡到面向对象。

作为开发人员,遵循编程规则本来就无可厚非,但是如果大家都遵循规则难免会有创新或者技术进步。有时候,在做某件事或者看到某个实现方案的时候,想想为什么要这么做,有没有更好的实现方案,这个编程或者做事习惯往往使得自己受益匪浅。

比如,我们都知道每个线程都有一个自己的栈,线程内的局部变量出了作用域就会被释放,那么有没有可能跨线程从另外一个线程去访问该线程的局部变量呢?其实,问题不算难,我们只需要尝试即可,但往往缺少的就是这种尝试。对于C++三大特性中的封装特性,如果直接访问私有变量,则编译器会报错,那么有没有其它方式可以访问私有变量呢?

今天,不妨试着反其道而行,尝试以其他方式破坏封装性,直接访问私有变量。

从一段代码说起

代码示例如下:

#include

classA{
public:
A()=default;
private:
intdata_=0;
};

intmain(){
Aa;
std::cout<< a.data_ << std::endl;

  return 0;
}

在gcc5.4下进行编译,不出所料,编译失败,报错如下:

test.cc:在函数‘intmain()’中:
test.cc:7:15:错误:‘int A::data_’是私有的
intdata_=0;

从报错信息看,因为data_成员变量是私有的,而通过对象访问私有成员变量是不被允许的,除了通过重新定义一个公共接口,在该接口内对data_进行访问外,但是这种方式并没有实现本文的目的即破坏封装性,那么有没有其它方式呢?

第一次尝试

c++标准中有这样一段描述:

The usual access checking rules do not apply to non-dependent names used to specify template arguments of the simple-template-id of the partial specialization. [ Note:The template arguments may be private types or objects that would normally not be accessible. Dependent names cannot be checked when declaring the partial specialization, but will be checked when substituting into the partial specialization. — end note ]

也就是说模板参数可以是某个类的私有类型,所以,我们可以借助此条标准继续实现我们的目的,代码如下:

#include

classA{
public:
A()=default;
private:
intdata_=0;
};

template
int&GetPrivateData(A&obj){
returnobj.*Member;
}

templateint&GetPrivateData<&A::data_>(A&);

intmain(){
Aobj;
GetPrivateData<&A::data>(obj);

return0;
}

在上述代码中,定义了一个函数模板,其模板参数为int A::*Member,功能是返回类A中的成员变量,编译后,报错如下:

test.cc:在函数‘intmain()’中:
test.cc:7:15:错误:‘int A::data_’是私有的
intdata_=0;
^
test.cc:22:3:错误:在此上下文中
GetPrivateData<&A::data_>(obj);

看来此方式还是行不通,只能另想它法。

第二次尝试

在上面的提示中,显示不能直接访问私有成员,标准提供了个方法,就是将需要访问类私有成员的函数或者类声明为friend。看到这块,你可能会想,有了friend用得着你教?。

很快写出如下这种代码:

classA{
public:
A()=default;
private:
intdata_=0;

friendintAccess(constA&a){
returna.data_;
}
};

intmain(){
Aa;
Access(a);

return0;
}

无疑,上面这种代码可以访问私有成员,但缺点是需要更改类实现,下面将介绍一种方式,其在不修改类本身定义的情况下实现访问私有成员变量。

本着大方向不变的原则,依然使用模板的方式访问私有成员,而对于上节中提示的非法访问私有成员,我也采用将对应函数声明为friend的方式。

#include

classA{
public:
A()=default;
private:
intdata_=0;
};

template< int A::*Member >
classAccess{
public:
friendintGetPrivateData(A&obj){
returnobj.*Member;
}
};

templateclassAccess<&A::data_>;

intGetPrivateData(A&);


intmain(){
Aobj;
GetPrivateData(obj);

return0;
}

编译 & 运行,OK!!!

另辟蹊径

在上一节实现中,使用了friend进行访问控制,所以在考虑有没有不使用friend的方式,于是在网上进行搜索查阅,如下:

classA{
public:
A(intnum):data_(num){};
private:
intdata_=0;
};

template
classAccess{
public:
inlinestaticPtrTypeptr;
};

template
structPtrTaker{
structTransferer{
Transferer(){
Access::ptr=T;
}
};
inlinestaticTransferertr;
};

templateclassPtrTaker<&A::data_>;//显示实例化

intmain(){
Aa{10};

intb=a.*Access::ptr;

return0;
}

说真的,看到这种实现方式的时候,一脸懵逼,尤其是对模板用的不多的情况下,阅读这短短几十行代码用了一天时间,其间也跟@Chunel骏哥哥一起讨论,奈何太挫了,只能硬着头皮自己研究,也跟群里的大佬们一起讨论了下,再结合自己的理解,分析下这块:

1、因为用到了inline 变量以及模板参数为auto,所以上述代码在cpp17上才可以运行。

2、以&A::data_作为模板参数,对类模板PtrTaker进行显示实例化,在显示实例化的时候,虽然不创建对象,但是对于其中存在的静态变量依然会进行初始化。因此会调用Transferer类的构造函数,从而对Access::ptr进行初始化

看上述代码的时候,一开始卡在了a.*Access::ptr这部分,后来经过跟其他技术大佬进行沟通,对这部分可以进行拆分简化:

•p = Access::ptr;

•a.*p

看了下面的代码示例,相信能便于理解:

classData{
public:
intnum_=0;
};

intmain(){
intData::*ptr=&Data::num_;

Datadata;
data.*ptr=10;

return0;
}

好了,我们接着进行讨论。

在使用对象访问成员的时候,其地址实际上分为两部分的,以a.data_为例(此处忽略访问控制权限),一部分是a的this指针,另一部分是data_成员在A结构里的偏移量,这个偏移量存储在&A::data_中。在上面的代码中,这个偏移量存储在静态数据ptr里了,即上面提到的Access::ptr。

所以,a.*p相当于如下:

intA::*p=&A::data_;
intoffset=*(longlong*)&p;
intdata=*(int*)((char*)&a+offset);

好了,截止为此,通过模板方式访问类私有成员的讨论结束了。

可能有人会有疑问,如果类有多个成员变量,又该如何访问呢,方式类似,代码如下:

#include
#include

classA{
public:
A(intnum,std::stringv):data_(num),value_(v){};
private:
intdata_=0;
std::stringvalue_;
};

template
classAccess{
public:
inlinestatictypenameTag::typeptr;
};

template
structPtrTaker{
structTransferer{
Transferer(){
Access::ptr=V;
}
};
inlinestaticTransferertr;
};

structTag1{
usingtype=intA::*;
};

structTag2{
usingtype=std::stringA::*;
};

templateclassPtrTaker;//显示实例化
templateclassPtrTaker;//显示实例化

intmain(){
Aa{0,"abc"};

std::cout<< "123 " << a.*Access::ptr;
}






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    180

    文章

    7534

    浏览量

    129772
  • 编译器
    +关注

    关注

    1

    文章

    1577

    浏览量

    48650
  • C++语言
    +关注

    关注

    0

    文章

    146

    浏览量

    6878
  • gcc编译器
    +关注

    关注

    0

    文章

    78

    浏览量

    3239

原文标题:访问私有成员——从技术实现的角度破坏"封装" 性

文章出处:【微信号:CPP开发者,微信公众号:CPP开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    CysecureTools如何直接访问外部存储设备内的密钥呢?

    显然不符合安全标准。 那么,如标题所示,CysecureTools应该如何直接访问外部存储设备内的密钥呢?\"
    发表于 01-31 08:08

    Labview通过VXI总线直接访问测量仪器内部寄存器

    VXI总线直接访问测量仪器内部寄存器,从而在线获取干涉仪的距离测量结果,从而与我的位移测量系统作比对。大家如果对激光干涉仪不了解不要紧,请告诉我如何通过VXI总线直接访问测量仪器内部寄存器,有遇上类似情况的吗? 本人对VXI及Labview了解不多,望有人能讨论和帮忙。不
    发表于 05-07 16:30

    1831B直接访问垂直操作说明

    1831B直接访问垂直操作说明
    发表于 10-26 11:52

    python私有变量私有方法

    下划线前导的方法,可以使用 实例._类名__方法名() 进行访问私有变量私有方法,虽然有办法访问,但是仍然不建议使用上面给出的方法
    发表于 03-08 16:30

    UCOS扩展例程-UCOSIII直接访问共享资源区

    UCOS扩展例程-UCOSIII直接访问共享资源区
    发表于 12-14 17:24 20次下载

    从汇编代码访问C全局变量

    在汇编代码中访问C全局变量,只能通过地址间接访问全局变量。要访问全局变量,必须在汇编中使用 I
    发表于 10-19 09:25 0次下载

    基于static变量实现一个模块的封装

    static变量的一个显著的作用就是可以实现一个模块的封装。 static存储类别的特性决定了static声明的全局变量只能被本源文件的函数引用。当在一个源文件中定义一个stat
    的头像 发表于 01-05 10:54 5332次阅读
    基于static<b class='flag-5'>变量</b>来<b class='flag-5'>实现</b>一个模块的<b class='flag-5'>封装</b>

    Python私有变量的定义方法

    在类内部使用,不被外部调用,且当变量被标记为私有后,调用时需再变量的前端插入类名,在类名前添加一个下划线,即“_ClassName__变量名”形式。Python
    发表于 02-13 16:49 1464次阅读

    C++中类的继承访问级别学习总结(二)

    上一篇文章我们介绍了c++中类的继承学习总结;今天我们继续来分享c++中类的继承中的访问级别的学习总结。一、继承中的访问级别学习:1、子类是否可以直接访问父类的私用成员吗?从面向对象理论角度
    的头像 发表于 12-24 16:10 519次阅读

    python私有变量私有方法

    python私有变量私有方法 1. 下划线妙用 在 Python 中,下划线可是非常推荐使用的符号: 变量名推荐使用下划线分隔的蛇形命名法 魔法方法、构造函数都需要使用双下划线 对于
    的头像 发表于 03-08 16:30 1809次阅读

    如何实现SIMATIC HMI对驱动参数的直接访问

    SINAMICS V90PN驱动器可以通过模拟S7-CPU,将数据库访问从HMI映射到驱动参数,该功能可实现在没有SIMATIC S7控制器时,SIMATIC HMI对驱动参数的直接访问
    的头像 发表于 08-10 17:59 1625次阅读
    如何<b class='flag-5'>实现</b>SIMATIC HMI对驱动参数的<b class='flag-5'>直接访问</b>呢

    封装是什么意思?封装有何意义?

    第二层:类中定义私有的属性和方法,只有类的内部能够调用(间接调用),外部无法直接访问
    的头像 发表于 08-18 15:54 9578次阅读
    <b class='flag-5'>封装</b>是什么意思?<b class='flag-5'>封装</b>有何意义?

    InfiniBand和远程直接访问是什么,如何进行配置

    本文简单描述了InfiniBand 和远程直接访问(RDMA)是什么,以及在实践中如何配置InfiniBand网络硬件。另外,本文档解释了如何配置与 InfiniBand 相关的服务。
    的头像 发表于 11-25 14:26 1289次阅读

    实现HMI直接访问驱动参数的方法

    SINAMICS V90PN驱动器可以通过模拟S7-CPU,将数据库访问从HMI映射到驱动参数,该功能可实现在没有SIMATIC S7控制器时,SIMATIC HMI对驱动参数的直接访问
    的头像 发表于 07-11 17:14 508次阅读
    <b class='flag-5'>实现</b>HMI<b class='flag-5'>直接访问</b>驱动参数的方法

    STM32L4直接访问内存模块(DMA)介绍

    电子发烧友网站提供《STM32L4直接访问内存模块(DMA)介绍.pdf》资料免费下载
    发表于 08-01 10:15 1次下载
    STM32L4<b class='flag-5'>直接访问</b>内存模块(DMA)介绍