close

今天開始,「每日一題」欄目正式上線,我爭取每天在朋友圈發一個問題,邀請大家討論,然後在公眾號里公布答案。

特此聲明:這裡的每日一題,是指每日最多一題,而不是每日至少一題

這是今天的問題:

我這裡再詳細描述一下:

首先有一個類A:

structA{private:structAImpl;AImpl*impl;};

還有一個類B:

structB{ voidfunc(A*a){ impl->func(a);}private: structBImpl; BImpl*impl;};

在源文件中還有個BImpl類:

structBImpl{ voidfunc(A*a){ ///<這裡想要拿到a->impl,該怎麼做 }};

在上面的代碼中,用到了pimpl模式,關於此模式的介紹可以看我的這篇文章pimpl設計模式。

其中A類和B類是暴露給外部的文件,只暴露一些應該給外部調用的接口,然後所有實現都放在內部的Impl類中。而且Impl中還包含了很多沒有對外暴露的功能。

所以這裡的大體邏輯是:對外暴露一個指針,對內使用它的impl指針。

但這裡有個問題:在上面的代碼中,impl指針是private變量,一個對象指針傳遞出去,再傳回內部,怎麼拿到它的impl指針呢?

說到這裡,可能有些朋友會說,不會有這個問題,出現這種問題肯定是設計的不合理,然而這裡我們暫且不討論設計層面的東西,單純的看看怎麼能夠解決這個問題。(由於業務的需求複雜性,導致我不得不採用這種設計,當然,更多的應該是因為我水平有限,沒有想到更好的設計。)

我發完這個問題後,有很多朋友都私聊我一起討論,最終總結出了三種答案。

方案1:友元。

把B類設置為A類的友元,然後在B中可以獲得A類的private成員,然後改變一下BImpl中func的參數,變成這樣:

structB{ voidfunc(A*a){ impl->func(a->impl);}private: structBImpl; BImpl*impl;};structBImpl{voidfunc(AImpl*a){ ///<}};

Impl側接收Impl類型的指針,這樣似乎更合理一些。

方案2:強轉

A::AImpl* impl = (A::AImpl)((long)a);

因為A的impl是類A的第一個成員變量,所以類A的對象地址和成員變量impl的地址相同,利用此原則就可以直接把a指針強轉成Impl指針。

這裡可能大家還會有些疑問,如果類A中有虛函數表怎麼辦?

其實不會出現這個問題,因為pimpl模式本質就是為了隱藏實現而生。隱藏實現大體就兩種方式,接口繼承或者pimpl,用pimpl就沒必要用繼承,而用了繼承就沒必要用pimpl。

我們再假設A有虛函數表,也沒啥太大問題,大不了判斷一下A是否有虛函數(可研究下type_traits),然後改變一下指針偏移量再去做強轉,這樣也行。

方案3:還是強轉,利用二維指針轉成一維指針

原理和方案2類似。

貼一個大佬的原話:

考慮到機器有可能是32位或64位字長,long類型與機器字長是相同,所以先轉為long指針,目的是為了取其值。而此方案更妙。

A::AImpl* impl = *(A::AImpl**)a;

其實個人認為後兩種方案沒啥區別,大家怎麼看?


往期推薦



C++服務性能優化的道與術-道篇:阿姆達爾定律

壓箱底的音視頻學習資料以及面經整理

C++ Best Practices (C++最佳實踐)翻譯與閱讀筆記

萬字長文 | STL 算法總結

2021程序喵技術年貨

【性能優化】lock-free在召回引擎中的實現

SDK開發的一些思考

定下來了!

Linux中對【庫函數】的調用進行跟蹤的 3 種【插樁】技巧

【線上問題】P1級公司故障,年終獎不保

探索CPU的調度原理

咳咳,說下開源項目這件事的進展

想拉着大家搞點事!

防禦性編程技巧

C++的全鏈路追蹤方案,稍微有點高端

喵哥吐血整理:軟件開發的51條建議

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()