摘要訊息 : 合法地在 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++ 標準的錯誤.