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的一個對象的內存布局類似於這樣:

image

 

指針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的一個對象的內存布局應該類似於這樣:

image

需要將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才可以


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM