privateメンバの合法的アクセス
#define private public - 神様なんて信じない僕らのためにを読んで、Exceptional C++ Style*1を思い出した。
そこでは、
- 偽造犯
- スリ
- 詐欺師
- 言語法律家
にたとえてprivateメンバのアクセス方法を説明している。この中で#define private publicはスリとして紹介されている。この方法はいろいろ遊んだなぁ。#define if whileとかやって、if (true) ...みたいな*2。
で、内容はというと、
// ファイルx.h // class X { public: X() : private_(1){/*...*/} template<class T> void f(const T& t){/*...*/} int Value() {return private_;} // ... private: int private_; };Exceptional C++ Style
のprivate_に直接アクセスする方法を示せ、というもの。ちょっとVC8で試してみよう。
まず、偽造犯。偽造犯では、x.hをインクルードせずに、自分でクラスXを書いてしまうという。
#include <iostream> class X; void access_private(X& x); class X { public: X() : private_(1){/*...*/} template<class T> void f(const T& t){/*...*/} int Value() {return private_;} private: int private_; // これを追加 friend void ::access_private(X&); }; void access_private(X& x) { // \(^o^)/ x.val = 100; } int main() { X x; access_private(x); std::cout << x.Value() << std::endl; }
これはODR*3に違反する。
次にスリ。これは上でも言ったとおり、#define public privateとするだけ。これもODR違反、さらに予約語を#defineするのは非合法でもある。
次は詐欺師。これは、オブジェクトのデータレイアウトが同じだろうという予想を使用する。
#include <iostream> #include "x.h" struct FakeX { int public_; }; void access_private(X& x) { // \(^o^)/ (reinterpret_cast<FakeX&>(x)).public_ = 100; } int main() { X x; access_private(x); std::cout << x.Value() << std::endl; }
今手元に仕様が無いのでアレなんだけど、reinterpret_castの結果は未定義なんだってさ。これは後で確認しておこう。
最後に言語法律家。なんというか、template怖い。
#include <iostream> #include "x.h" namespace { struct Y {}; } template <> void X::f(const Y&) { // \(^o^)/ this->private_ = 100; } int main() { X x; x.f(Y()); std::cout << x.Value() << std::endl; }
クラスXがtemplateのメンバ関数を持っていることを利用して、無名名前空間内のYで特殊化している。C++のtemplateは柔軟すぎて怖いよ、本当。
結論:Exceptional C++ Styleは読むべき