読者です 読者をやめる 読者になる 読者になる

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は読むべき

*1:15. アクセス制御の使用と誤用

*2:良い子も悪い子も真似しちゃ駄目です。ちなみに、VC8だとこれやってもcoutが普通に使えた。

*3:One Definition Rule