在C++教材的前幾章就介紹了其特性,即:C++是一門面向?qū)ο笳Z言,具有封裝、繼承和多態(tài)三個特點。后來,隨著編碼的增多以及工作經(jīng)驗的積累,對個概念的理解越來越深。編碼習(xí)慣也嚴格按照相應(yīng)的規(guī)則,該封裝的時候進行封裝,該繼承的時候進行繼承,以使得編程思維從之前的面向過程逐步過渡到面向?qū)ο蟆?/p>
作為開發(fā)人員,遵循編程規(guī)則本來就無可厚非,但是如果大家都遵循規(guī)則難免會有創(chuàng)新或者技術(shù)進步。有時候,在做某件事或者看到某個實現(xiàn)方案的時候,想想為什么要這么做,有沒有更好的實現(xiàn)方案,這個編程或者做事習(xí)慣往往使得自己受益匪淺。
比如,我們都知道每個線程都有一個自己的棧,線程內(nèi)的局部變量出了作用域就會被釋放,那么有沒有可能跨線程從另外一個線程去訪問該線程的局部變量呢?其實,問題不算難,我們只需要嘗試即可,但往往缺少的就是這種嘗試。對于C++三大特性中的封裝特性,如果直接訪問私有變量,則編譯器會報錯,那么有沒有其它方式可以訪問私有變量呢?
今天,不妨試著反其道而行,嘗試以其他方式破壞封裝性,直接訪問私有變量。
從一段代碼說起
代碼示例如下:
#includeclassA{ public: A()=default; private: intdata_=0; }; intmain(){ Aa; std::cout<
在gcc5.4下進行編譯,不出所料,編譯失敗,報錯如下:
test.cc:在函數(shù)‘intmain()’中: test.cc:7:15:錯誤:‘int A::data_’是私有的 intdata_=0;
從報錯信息看,因為data_成員變量是私有的,而通過對象訪問私有成員變量是不被允許的,除了通過重新定義一個公共接口,在該接口內(nèi)對data_進行訪問外,但是這種方式并沒有實現(xiàn)本文的目的即破壞封裝性,那么有沒有其它方式呢?
第一次嘗試
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 ]
也就是說模板參數(shù)可以是某個類的私有類型,所以,我們可以借助此條標準繼續(xù)實現(xiàn)我們的目的,代碼如下:
#includeclassA{ 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; }
在上述代碼中,定義了一個函數(shù)模板,其模板參數(shù)為int A::*Member,功能是返回類A中的成員變量,編譯后,報錯如下:
test.cc:在函數(shù)‘intmain()’中: test.cc:7:15:錯誤:‘int A::data_’是私有的 intdata_=0; ^ test.cc:22:3:錯誤:在此上下文中 GetPrivateData<&A::data_>(obj);
看來此方式還是行不通,只能另想它法。
第二次嘗試
在上面的提示中,顯示不能直接訪問私有成員,標準提供了個方法,就是將需要訪問類私有成員的函數(shù)或者類聲明為friend??吹竭@塊,你可能會想,有了friend用得著你教?。
很快寫出如下這種代碼:
classA{ public: A()=default; private: intdata_=0; friendintAccess(constA&a){ returna.data_; } }; intmain(){ Aa; Access(a); return0; }
無疑,上面這種代碼可以訪問私有成員,但缺點是需要更改類實現(xiàn),下面將介紹一種方式,其在不修改類本身定義的情況下實現(xiàn)訪問私有成員變量。
本著大方向不變的原則,依然使用模板的方式訪問私有成員,而對于上節(jié)中提示的非法訪問私有成員,我也采用將對應(yīng)函數(shù)聲明為friend的方式。
#includeclassA{ public: A()=default; private: intdata_=0; }; template classAccess{ public: friendintGetPrivateData(A&obj){ returnobj.*Member; } }; templateclassAccess<&A::data_>; intGetPrivateData(A&); intmain(){ Aobj; GetPrivateData(obj); return0; }
編譯 & 運行,OK?。?!
另辟蹊徑
在上一節(jié)實現(xiàn)中,使用了friend進行訪問控制,所以在考慮有沒有不使用friend的方式,于是在網(wǎng)上進行搜索查閱,如下:
classA{ public: A(intnum):data_(num){}; private: intdata_=0; }; templateclassAccess{ public: inlinestaticPtrTypeptr; }; template structPtrTaker{ structTransferer{ Transferer(){ Access ::ptr=T; } }; inlinestaticTransferertr; }; templateclassPtrTaker<&A::data_>;//顯示實例化 intmain(){ Aa{10}; intb=a.*Access ::ptr; return0; }
說真的,看到這種實現(xiàn)方式的時候,一臉懵逼,尤其是對模板用的不多的情況下,閱讀這短短幾十行代碼用了一天時間,其間也跟@Chunel駿哥哥一起討論,奈何太挫了,只能硬著頭皮自己研究,也跟群里的大佬們一起討論了下,再結(jié)合自己的理解,分析下這塊:
1、因為用到了inline 變量以及模板參數(shù)為auto,所以上述代碼在cpp17上才可以運行。
2、以&A::data_作為模板參數(shù),對類模板PtrTaker進行顯示實例化,在顯示實例化的時候,雖然不創(chuàng)建對象,但是對于其中存在的靜態(tài)變量依然會進行初始化。因此會調(diào)用Transferer類的構(gòu)造函數(shù),從而對Access::ptr進行初始化
看上述代碼的時候,一開始卡在了a.*Access
::ptr 這部分,后來經(jīng)過跟其他技術(shù)大佬進行溝通,對這部分可以進行拆分簡化:?p = Access
::ptr; ?a.*p
看了下面的代碼示例,相信能便于理解:
classData{ public: intnum_=0; }; intmain(){ intData::*ptr=&Data::num_; Datadata; data.*ptr=10; return0; }
好了,我們接著進行討論。
在使用對象訪問成員的時候,其地址實際上分為兩部分的,以a.data_為例(此處忽略訪問控制權(quán)限),一部分是a的this指針,另一部分是data_成員在A結(jié)構(gòu)里的偏移量,這個偏移量存儲在&A::data_中。在上面的代碼中,這個偏移量存儲在靜態(tài)數(shù)據(jù)ptr里了,即上面提到的Access::ptr。
所以,a.*p相當于如下:
intA::*p=&A::data_; intoffset=*(longlong*)&p; intdata=*(int*)((char*)&a+offset);
好了,截止為此,通過模板方式訪問類私有成員的討論結(jié)束了。
可能有人會有疑問,如果類有多個成員變量,又該如何訪問呢,方式類似,代碼如下:
#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?"?<::ptr; }
審核編輯:劉清
-
C語言
+關(guān)注
關(guān)注
180文章
7604瀏覽量
136686 -
編譯器
+關(guān)注
關(guān)注
1文章
1623瀏覽量
49108 -
C++語言
+關(guān)注
關(guān)注
0文章
147瀏覽量
6987 -
gcc編譯器
+關(guān)注
關(guān)注
0文章
78瀏覽量
3378
原文標題:訪問私有成員——從技術(shù)實現(xiàn)的角度破壞"封裝" 性
文章出處:【微信號:CPP開發(fā)者,微信公眾號:CPP開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論