摘要訊息 : 合法地在 C++ 中竊取類別的私用成員.
0. 前言
在 C++ 中, 私用成員是不允許直接訪問的, 例如下列程式碼就會產生編碼錯誤 :
struct s {
private:
int data {100};
};
int main(int argc, char *argv[]) {
s p;
auto a {p.data}; // Error : 'data' is a private member of 's'
}
不過有人發現, 我們可以通過樣板來竊取私用成員.
更新紀錄 :
- 2022 年 6 月 6 日進行第一次更新和修正.
1. 竊取私用成員
一般來說, 如果我們想要獲取 Code 1 中, 類別 s
的成員 data
的值, 我們會實作一個存取介面成員函式, 例如 get_data
等. 這個不算是竊取, 就是通過介面來正常存取. 真正的竊取是將 Code 1 中的 p
通過強制型別轉換, 以指標的方式竊取. 例如 *reinterpret_cast<int *>(&p)
就可以在大多數編碼器上竊取到 data
的值. 然而這個做法並不合法, 並且也不具有可攜性.
要合法地竊取私用成員, 就要想到 C++ 標準中的一條規定 : 在樣板明確具現化的過程中, 不考慮訪問限制. 也就是說, 當我們手動具現化某個樣板的時候, 就可以不考慮類別中的訪問限制. 那麼有些人就想出了如下的虛擬碼 :
template <T *Class::MemPtr>
struct stealer;
template struct stealer<&class_name::class_member>;
在 Code 2 中, 由於在樣板中編碼器不會考慮類別 Class
中的訪問限制, 因此 stealer 有機會可以直接竊取到私用成員. 於是, 我們想到 :
#include <iostream>
struct s {
private:
int data {100};
};
template <int s::*data>
struct stealer {
void steal(const s *p) noexcept {
std::cout << p->*data << std::endl;
}
};
int main(int argc, char *argv[]) {
s p;
//auto a {p.data}; // Error : 'data' is a private member of 's'
stealer<&s::data> {}.steal(&p);
}
但是, Code 3 同樣不可行. 因為 stealer<&s::data> steal(&p);
並不是手動具現化. 因此, 我們並不能宣告 stealer
的具現體. 但是如果不宣告 stealer
的具現體, 應該如何直接呼叫成員函式 steal
呢? 我們想到使用友誼函式 :
#include <iostream>
struct s {
private:
int data {100};
};
void steal(const s *) noexcept;
template <int s::*data>
struct stealer {
friend void steal(const s *p) noexcept {
std::cout << p->*data << std::endl;
}
};
template struct stealer<&s::data>;
int main(int argc, char *argv[]) {
s p;
//auto a {p.data}; // Error : 'data' is a private member of 's'
steal(&p); // 輸出 : 100
}
這樣, 我們就竊取到了類別 s
的私用成員 data
. 除了成員變數之外, 成員函式同樣可行 :
#include <iostream>
struct s {
private:
int data {100};
void print() {
cout << 1 << endl;
}
public:
void set(int data) noexcept {
this->data = data;
}
};
void steal(const s *) noexcept;
template <int s::*data>
struct stealer {
friend void steal(const s *p) noexcept {
std::cout << p->*data << std::endl;
}
};
template struct stealer<&s::data>;
void steal(s &p) noexcept;
template <void (s::*memfun)()>
struct memfun_stealer {
friend void steal(s &p) noexcept {
(p.*memfun)();
}
};
template struct memfun_stealer<&s::print>;
int main() {
s p;
//auto a {p.data}; // Error : 'data' is a private member of 's'
steal(&p); // 輸出 : 100
p.set(200);
steal(&p); // 輸出 : 200
steal(p); // 輸出 : 1
}
樣板配合友誼函式貌似是 C++ Bug 頻出的地方, 上有 Stateful Meta-Programming, 下有竊取私用成員. 不過準確地來說, 這確確實實是 C++ 標準有失誤的地方, 不能說是一個 C++ 標準的錯誤.
自創文章, 原著 : Jonny. 如若閣下需要轉發, 在已經授權的情況下請註明本文出處 :