如何在C++類的外部調(diào)用類的私有方法
前言
可否在C++類的外部調(diào)用類的私有方法呢?既然談到了這個(gè)問題,當(dāng)然是可以的。
問題
目標(biāo)是在一個(gè)外部function中調(diào)用Widget::forbidden()這個(gè)private function。限制條件是不能修改Widget類。
class Widget { private: void forbidden(); }; void hijack(Widget& w) { w.forbidden(); // ERROR! }
下面我們一步步來實(shí)現(xiàn)這個(gè)小目標(biāo):
技術(shù)準(zhǔn)備
顯然,我們不可能直接像w.forbidden()類似那樣調(diào)用,我們必須通過PMF(pointer to member function)來間接調(diào)用,所以,下面先簡要介紹PMF:
1. pointers to member functions
由于下面會(huì)廣泛的用到pointers to member functions (PMFs),我們先來回顧一下它的用法:
class Calculator { float current_val = 0.f; public: void clear_value() { current_val = 0.f; }; float value() const { return current_val; }; void add(float x) { current_val += x; }; void multiply(float x) { current_val *= x; }; };
在C++11中,使用函數(shù)指針調(diào)用函數(shù),我們可以這么做:
using Operation = void (Calculator::*)(float); Operation op1 = &Calculator::add; Operation op2 = &Calculator::multiply; using Getter = float (Calculator::*)() const; Getter get = &Calculator::value; Calculator calc{}; (calc.*op1)(123.0f); // Calls add (calc.*op2)(10.0f); // Calls multiply // Prints 1230.0 std::cout << (calc.*get)() << '\n';
函數(shù)指針的一個(gè)特性是它可以綁定到類的是由成員函數(shù),下面我們將會(huì)用到這點(diǎn),假設(shè)Widget類提供了某種機(jī)制來獲取其私有成員函數(shù)的方法,那么,實(shí)現(xiàn)我們的目標(biāo)就可以像下面這樣做:
class Widget { public: static auto forbidden_fun() { return &Widget::forbidden; } private: void forbidden(); }; void hijack(Widget& w) { using ForbiddenFun = void (Widget::*)(); ForbiddenFun const forbidden_fun = Widget::forbidden_fun(); // Calls a private member function on the Widget // instance passed in to the function. (w.*forbidden_fun)(); }
采用這種辦法不錯(cuò),但是別忘了,我們不能修改Widget類,大多數(shù)的類也不提供返回私有成員的方法。
既然我們不能通過添加函數(shù)返回PMF的方法來進(jìn)行實(shí)踐,那么,是否還有其它方法可以在類的外部訪問類的私有成員呢?C++提供了顯式模板特化的方法。
2. The explicit template instantiation
C++ 標(biāo)準(zhǔn)中提到:
17.7.2 (item 12)
The usual access checking rules do not apply to names used to specify explicit instantiations. [Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible.]
顯式模板特化時(shí),名字訪問規(guī)則此時(shí)不起作用,Stack Overflow 給出了一個(gè)例子:
class Foo { private: struct Bar; template<typename T> class Baz { }; public: void f(); // does things with Baz<Bar> }; // explicit instantiation declaration extern template class Foo::Baz<Foo::Bar>;
這里在顯式模板特化Foo::Baz時(shí),可以訪問Foo::Baz和Foo::bar
到這里,既然成員訪問規(guī)則不適用于模板特化,模板特化時(shí)可以訪問類的私有成員,那么,我們可否將PMF作為模板參數(shù)呢,下面將介紹這點(diǎn)。
3. Passing a member-function pointer as a non-type template parameter
在C++中模板參數(shù)通常分為兩類:
- 類型參數(shù),一般的參數(shù)都是類型參數(shù),用來進(jìn)行類型替換;
- 非類型參數(shù),常見的是整型或指針類型的非類型參數(shù)
比如下面,其中T是類型參數(shù),size是非類型參數(shù):
template <class T, int size> // size is the non-type parameter class StaticArray { private: // The non-type parameter controls the size of the array T m_array[size]; public: T* getArray(); T& operator[](int index) { return m_array[index]; } };
下面我們再看一個(gè)指針作為非類型參數(shù)的例子:
class SpaceShip { public: void dock(); // ... }; // Member function alias that matches the // signature of SpaceShip::dock() using SpaceShipFun = void (SpaceShip::*)(); // spaceship_fun is a pointer-to-member-function // value which is baked-in to the type of the // SpaceStation template at compile time. template <SpaceShipFun spaceship_fun> class SpaceStation { // ... }; // Instantiate a SpaceStation and pass in a // pointer to member function statically as a // template argument. SpaceStation<&SpaceShip::dock> space_station{};
上面的別名SpaceShipFun的使用使得SpaceStation只能用SpaceShip::dock的PMF來實(shí)例化,這個(gè)模板顯得不那么通用,所以我們可以將函數(shù)指針也作為一個(gè)模板參數(shù):
template < typename SpaceShipFun, SpaceShipFun spaceship_fun > class SpaceStation { // ... }; // Now we must also pass the type of the pointer to // member function when we instantiate the // SpaceStation template. SpaceStation< void (SpaceShip::*)(), &SpaceShip::dock > space_station{};
當(dāng)然,我們也可以更進(jìn)一步,在實(shí)例化模板的時(shí)候讓編譯器自動(dòng)推導(dǎo)函數(shù)指針的類型:
SpaceStation< decltype(&SpaceShip::dock), &SpaceShip::dock > space_station{};
到這里,我們似乎找到了解決方案,通過顯示模板特化導(dǎo)出本來對外部不可見的函數(shù)指針,通過函數(shù)指針調(diào)用是由函數(shù)
4. Solution. Passing a private pointer-to-member-function as a template parameter
我們結(jié)合上面的3個(gè)技巧,似乎找到了解決方案:
// The first template parameter is the type // signature of the pointer-to-member-function. // The second template parameter is the pointer // itself. template < typename ForbiddenFun, ForbiddenFun forbidden_fun > struct HijackImpl { static void apply(Widget& w) { // Calls a private method of Widget (w.*forbidden_fun)(); } }; // Explicit instantiation is allowed to refer to // `Widget::forbidden` in a scope where it's not // normally permissible. template struct HijackImpl< decltype(&Widget::forbidden), &Widget::forbidden >; void hijack(Widget& w) { HijackImpl< decltype(&Widget::forbidden), &Widget::forbidden >::apply(w); }
運(yùn)行一下,發(fā)現(xiàn)其實(shí)不可行:
error: 'forbidden' is a private member of 'Widget' HijackImpl<decltype(&Widget::forbidden), &Widget::forbidden>::hijack(w);
主要的原因是因?yàn)樵趆ijack函數(shù)中使用HijackImpl<decltype(&Widget::forbidden), &Widget::forbidden>::apply(w)不是顯式模板特化,它只是常見的隱式模板實(shí)例化。
那么,如何采用顯示模板特化的方法你?這就需要使用friend技巧:
5. Friend
我們通常這樣定義和使用友員函數(shù):
class Gadget { // Friend declaration gives `frobnicate` access // to Gadget's private members. friend void frobnicate(); private: void internal() { // ... } }; // Definition as a normal free function void frobnicate() { Gadget g; // OK because `frobnicate()` is a friend of // `Gadget`. g.internal(); }
但是不會(huì)這樣:
class Gadget { // Free function declared as a friend of Gadget friend void frobnicate() { Gadget g; g.internal(); // Still OK } private: void internal(); }; void do_something() { // NOT OK: Compiler can't find frobnicate() // during name lookup frobnicate(); }
因?yàn)閒robnicate在Gadget內(nèi)部,do_something()在做名字查找時(shí)不會(huì)查找到frobnicate,解決辦法如下:
class Gadget { friend void frobnicate(Gadget& gadget) { gadget.internal(); } private: void internal(); }; void do_something(Gadget& gadget) { // OK: Compiler is now able to find the // definition of `frobnicate` inside Gadget // because ADL adds it to the candidate set for // name lookup. frobnicate(gadget); }
當(dāng)然如果do_something不帶參數(shù),我們在外部重新聲明友員函數(shù),也是可以的:
class Gadget { // Definition stays inside the Gadget class friend void frobnicate() { Gadget g; g.internal(); } private: void internal(); }; // An additional namespace-scope declaration makes // the function available for normal name lookup. void frobnicate(); void do_something() { // The compiler can now find the function frobnicate(); }
了解了這些,對下面這樣的代碼就不會(huì)太吃驚了:
#include <iostream> template <int N> class SpookyAction { friend int observe() { return N; } }; int observe(); int main() { SpookyAction<42>{}; std::cout << observe() << '\n'; // Prints 42 }
Put the magic pieces together
那么結(jié)合上面所有的這些技巧,我們就可以得到下面的代碼:
namespace { // This is a *different* type in every translation // unit because of the anonymous namespace. struct TranslationUnitTag {}; } void hijack(Widget& w); template < typename Tag, typename ForbiddenFun, ForbiddenFun forbidden_fun > class HijackImpl { friend void hijack(Widget& w) { (w.*forbidden_fun)(); } }; // Every translation unit gets its own unique // explicit instantiation because of the // guaranteed-unique tag parameter. template class HijackImpl< TranslationUnitTag, decltype(&Widget::forbidden), &Widget::forbidden >;
完整代碼可以參見這里:@wandbox
總結(jié)一下:
我們通過顯式模板特化,將Widget::forbidden的函數(shù)指針傳遞給了HijackImpl示例,特化了模板函數(shù)hijack通過友員函數(shù)聲明,讓hijack函數(shù)可以在他處被調(diào)用,從而達(dá)成了小目標(biāo) 結(jié)語
寫這些,是希望你將上面的代碼用于實(shí)際產(chǎn)品代碼嗎?絕對不要這么做! 不要違背C++的封裝的原則,一旦打開潘多拉魔盒,那么你的產(chǎn)品代碼雖然看起來可運(yùn)行,但是將不可維護(hù),不可知,因?yàn)槟闫茐牧宋覀兒虲++語言以及程序員之間的合約。
到此這篇關(guān)于如何在C++類的外部調(diào)用類的私有方法的文章就介紹到這了,更多相關(guān)C++類外部調(diào)用類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C/C++產(chǎn)生指定范圍和不定范圍隨機(jī)數(shù)的實(shí)例代碼
C/C++產(chǎn)生隨機(jī)數(shù)用到兩個(gè)函數(shù)rand() 和 srand(),這里介紹不指定范圍產(chǎn)生隨機(jī)數(shù)和指定范圍產(chǎn)生隨機(jī)數(shù)的方法代碼大家參考使用2013-11-11利用C++實(shí)現(xiàn)簡易的.ini配置文件解析器
這篇文章主要為大家詳細(xì)介紹了如何基于C++編寫一個(gè)簡易的.ini配置文件解析器,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解一下2023-03-03Cocos2d-x UI開發(fā)之場景切換代碼實(shí)例
這篇文章主要介紹了Cocos2d-x UI開發(fā)之場景切換代碼實(shí)例,cocos2d-x中的場景切換是通過導(dǎo)演類調(diào)用相應(yīng)的方法完成的,本文通過代碼和詳細(xì)注釋來說明,需要的朋友可以參考下2014-09-09實(shí)例講解C語言編程中的結(jié)構(gòu)體對齊
這篇文章主要介紹了C語言編程中的結(jié)構(gòu)體對齊,值得注意的是一些結(jié)構(gòu)體對齊的例子在不同編譯器下結(jié)果可能會(huì)不同,需要的朋友可以參考下2016-04-04