C++获取private的变量-偷走private

private提供了对数据的封装,使得private成员只能被类自身的成员函数以及类的友元访问,其他的函数或者类想要访问private成员只能通过该类所提供的set和get的方法进行访问,

或者返回其指针或引用(effective C++中提到过要避免返回对象内部构件的引用,指针,或迭代器。这样会提高封装性,帮助 const 成员函数产生 const 效果,并将悬空句柄产生的可能性降到最低,所以但这个方法并不是特别的好)

但如果你想获得一个类的private成员,但是该类的已经在项目被大量的使用,或者是因为其他的原因,你没有办法添加get和set方法时,又应该如何获得该类的private成员呢?

我总结出了以下几种方法

方法一:重定义

#define  private public

示例

A.h定义

#pragma once
class A
{
    int j;
public:
    A() :i(10), j(20)
    {

    }
    template<class Type>
    void show(Type t)
    {
        cout << "Hello" << endl;
    }
private:
    int i;
};

main.cpp

#include <iostream>
#define private public
#include "A.h"
using namespace std;


int main()
{
    A a;
    cout << a.i << endl;
    //cout<<a.j<<endl;无法获得
    system("pause");
    return 0;
}

该方法的优点的是简单但也有不少的缺点

1.如果在类的定义时不指定访问标号关键字(public,protected,private),使用默认的private访问限制,那么该方法就无法达到目的了,比如这里的j就无法获得

2.降低代码的可读性,改变的一个关键字的意义,没有注意到这一点的程序员会产生困扰

3.将所有使用了private访问的标号的成员的访问等级都变成了public,降低了数据的封装性

方法二:模拟内存法

A.h定义

#include <iostream>
#include "A.h"
using namespace std;


int main()
{
    A a;
    void *p = &a;
    cout << "j:" << *(int*)p << endl;
    cout << "i:" << *((int*)p+1)<< endl;// *(int*)((char*)p+4)
    system("pause");
    return 0;
}

C++标准中要求,在同一个访问区域中,成员的排列只需符合较晚出现的成员在类的成员中有较高的地址即可,成员之间可能会因为数据对齐所需,添加一些字节

目前各编译器都是吧一个以上的访问区域连在一起,安装声明的顺序成为一个连续的区域

所以类A的一个对象的内存布局类似于这样:

指针p指向j,将p加上一个int长度或者4个char长度就可以指向i了

但这个方法的缺点也很明显,需要程序员自己对类的内存布局有着较强的了解,考虑到数据对齐,虚函数,不同编译器的实现等等方面

比如以下两种情况

1、数据对齐

A.h定义

#pragma once
class A
{
    char j;
public:
    A() :i(10), j(20)
    {

    }
    template<class Type>
    void show(Type t)
    {
        cout << "Hello" << endl;
    }
private:
    int i;
    
};

char j占用了一个byte,而i为了数据对齐,在内存布局上并不是与j紧挨着的,而是隔了3个byte,

所以获得i和j的间隔与上一个一样,只是j的类型变了

#include <iostream>
#include "A.h"
using namespace std;


int main()
{
    A a;
    void *p = &a;
    cout << "j:" << *((char*)p) << endl;
    cout << "i:" << *((int*)p+1)<< endl;
    system("pause");
    return 0;
}

2.加入虚函数

A.h定义

#pragma once
class A
{
    int j;
public:
    A() :i(10), j(20)
    {

    }
    virtual void show()
    {

    }
    template<class Type>
    void show(Type t)
    {
        cout << "Hello" << endl;
    }
private:
    int i;
    
};

编译器为了支持虚函数,会在类的每一个对象中,产生一个额外的虚函数指针指向相应的虚函数表,不同的编译器对这个指针处理不同,有点将它放在了类对象的尾端,有的将它放在了类对象的开始处

vs2013将它放在了类的开头处

所以类A的一个对象的内存布局应该类似于这样:

需要将p加上4个字节后才能指向j

#include <iostream>
#include "A.h"
using namespace std;


int main()
{
    A a;
    void *p = &a;
    cout << "j:" << *((int*)p+1) << endl;
    cout << "i:" << *((int*)p+2)<< endl;
    system("pause");
    return 0;
}

所以如果虚函数过多,又加入了虚继承, 类里面又有大量程序员自己定义的类型,那么该方法就会很麻烦了。

方法三:李代桃僵

A.h的定义

#pragma once
class A
{
    char j;
public:
    A() :i(10), j('b')
    {

    }
    virtual void show()
    {

    }
    template<class Type>
    void show(Type t)
    {
        cout << "Hello" << endl;
    }
private:
    int i;
    
};

李代桃僵法是模拟内存布局的另一个实现方式

我们看到现在A里有一个虚函数,一个j和一个i

如果直接使用模拟内存法的话会很麻烦

所以我们可以另声明一个对象B,它的内存布局和A的一样,只是i和j的访问限制变成了public

这样我们可以把一个指向A的对象的指针当做一个指向B的对象指针来使用

#include <iostream>
#include "A.h"
using namespace std;

class B
{
public:
    char j;
public:
    B() :i(10), j('b')
    {

    }
    virtual void show()
    {

    }
    /*template<class Type>
    void show(Type t)
    {
        cout << "Hello" << endl;
    }*/
public:
    int i;

};
int main()
{
    A a;
    B *b = (B*)&a;
    cout<<"j:" << b->j << endl;
    cout<<"i:" << b->i << endl;
    system("pause");
    return 0;
}

非虚成员函数show放在函数段中,并不在类对象的布局中占用空间,所以有没有show函数都可以

因为B的对象的内存布局与A一样,只是访问限制不同,所以可以利用对B对象的规则去访问A的对象

一个指向B对象的指针实际指向了一个A对象,对B中j和i的访问实际上是对A对象中i和j的访问

该方法模拟内存法容易了很多,但你需要额外声明一个B对象的定义,而且必须要确保B对象的内存布局要与A对象的一致

方法四 特化函数模板法

a.h的定义

#pragma once
class A
{
    char j;
public:
    A() :i(10), j('b')
    {

    }
    virtual void show()
    {

    }
    template<class Type>
    void show(Type t)
    {
        cout << "Hello" << endl;
    }
private:
    int i;
    
};

这里我们发现A有个函数模板show,所以我们可以利用对函数模板show进行特化的方式合法的获得i和j的public访问权限

#include <iostream>
#include "A.h"
using namespace std;


class B
{

};
template<>
void A::show(B b)
{
    cout << "j:"<<this->j << endl;
    cout << "i:" << this->i << endl;
}
int main()
{
    A a;
    a.show(B());
    system("pause");
    return 0;
}

该方法合理,简单,但也有缺点就是相应的类必须要有成员模板,并且该模板的访问限制为public才可以

总结

方法

优点

缺点

可移植性

重定义

简单

1.如果在类的定义时不指定访问标号关键字(public,protected,private),使用默认的private访问限制,那么该方法就无法达到目的了,比如这里的j就无法获得 2.降低代码的可读性,改变的一个关键字的意义,会没有注意到这一点的程序员照成困扰 3.将所有使用了private访问的标号的成员的访问等级都变成了public,降低了数据的封装性

模拟内存法

虚函数过多,又加入了虚继承, 类里面又有大量程序员自己定义的类型时,那么该方法就会很麻烦了。需要程序员对内存布局有较深的认识

李代桃僵

简单,可能在有些人看来比较清楚

需要额外声明一个B对象的定义,而且必须要确保B对象的内存布局要与想要访问的A对象的一致

特化函数模板法

合理,简单

相应的类必须要有成员模板,并且该模板的访问限制为public才可以

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端知识分享

第193天:js---Math+Error+Number+Object总结

8320
来自专栏专注数据中心高性能网络技术研发

[Effective Modern C++(11&14)]Chapter 2: auto

auto自动类型推导可以精简代码,避免隐式转换带来开销,同时增强程序可移植性和减少重构复杂性;但也由于与隐式代理类的冲突,造成了一些潜在问题,但是这些问题不是a...

35470
来自专栏企鹅号快讯

看完这篇文章我知道至少85%的人是没有入门Python的!花两周整理

以前刚学编程的时候就对Python略有耳闻,不过学校只有C,C++,Java,C#。和PHP有句"PHP是最好的语言" 这种家喻户晓的骚话一样,Python也有...

30370
来自专栏用户2442861的专栏

Java中Synchronized的用法

原文:http://blog.csdn.net/luoweifu/article/details/46613015 作者:luoweifu 转载请标名...

7510
来自专栏向治洪

Promise机制详解

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的...

35770
来自专栏小二的折腾日记

面试总结-C++

堆、栈、自由存储区、全局/静态存储区、常量存储区 自由存储区存储malloc申请的内存 (1)从静态存储区域分配 。内存在程序编译的时候就已经分配好,这块内存在...

21110
来自专栏xx_Cc的学习总结专栏

C - 基础总结

366110
来自专栏Jerry的SAP技术分享

使用javap深入理解Java整型常量和整型变量的区别

下面我们就用javap将.class文件反编译出来然后深入研究Java里整型变量和整型常量的区别。

15530
来自专栏向治洪

Promise机制

Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的...

230100
来自专栏数据结构笔记

数据结构(一):什么是数据结构

数据的逻辑结构是从逻辑关系上描述数据(主要是相邻关系,比如栈、队列、链表等),它与数据的存储无关,是独立于计算机的。因此,数据结构可以看作从具体问题中抽象...

20940

扫码关注云+社区

领取腾讯云代金券