🐸
インスタンスメソッドはどこにある?
0. はじめに
クイズです。下記のプログラムの出力はどうなるでしょうか?
#include <cstdio>
struct A
{
int val;
int GetVal() { return val; };
};
int main()
{
A a0, a1;
printf("%s\n", &a0 == &a1 ? "Yes" : "No"); // Q1
printf("%s\n", &a0.val == &a1.val ? "Yes" : "No"); // Q2
printf("%s\n", &a0.GetVal == &a1.GetVal ? "Yes" : "No"); // Q3
printf("%s\n", &a0.GetVal == &A::GetVal ? "Yes" : "No"); // Q4
}
答えは、
No
No
Yes
Yes
です。
1. インスタンスメソッドはクラスメソッドと同じ場所
-
インスタンス自体は別のアドレスを指します(Q1)
→ 教科書通りです。 -
インスタンス変数も別のアドレスを指します(Q2)
→ 教科書通りです。 -
インスタンスメソッドは同じメモリを指します(Q3)
→ これは当たり前ですか? -
クラスメソッドとインスタンスメソッドは同じメモリを指します(Q4)
→ これは当たり前ですか?
"インスタンスはクラスを実体化したもの"と覚えた私には、Q3,Q4は意外でした。
つまりstruct A
を書き直すと、以下と同じですね。
struct A
{
int val;
};
int GetVal(A &a) { return a.val; };
C言語っぽいインターフェースになりました。
2. Pimplに一瞬感動した
ちょっと話題が変わりますが、クラスの内部構造を隠したいためにPimplイデオムがあるのは有名ですね。
初めて知ったときは少し感動しました。
先ほどのstruct A
を例に書いてみます。
// A.h
class A
{
public:
A();
~A();
int GetVal();
private:
class Aimp;
Aimp *aimp;
};
// A.cpp
class A::Aimp
{
public:
Aimp():val(0) {}
~Aimp() {}
int GetVal() { return val; }
private:
int val;
};
A::A():aimp(new A::Aimp()) {}
A::~A() { delete aimp; }
A::GetVal() { return aimp->GetVal(); }
// main
#include <cstdio>
#include "A.h"
int main()
{
A a;
printf("%d\n", a.GetVal());
}
複雑ですね。
class Aimp
で前方宣言するのがポイントで、class A
は実体のラッパーです。
オブジェクト指向プログラミング(以降OOP)にこだわりが無ければ、ラッパーを外すとシンプルになりそうです。
書いてみます。
// A.h
struct Aimp;
int GetVal(Aimp *aimp);
// A.cpp
struct Aimp
{
int val = 0;
};
int GetVal(Aimp *aimp) { return aimp-> val; }
// main
#include <cstdio>
#include "A.h"
int main()
{
A a;
printf("%d\n", GetVal(&a));
}
簡単で、これまたC言語っぽいインターフェースになりました。
3. 何が言いたいのか
OOPでプログラミングを初めて覚えて、OOPを使い続けていると、OOP前提で実装や設計を考えてしまう脳みそが出来上がってしまうのではと思います。
OOP反対と言いたいのではないです。
OOPならではの素晴らい点もありますが、OOPを使わないければもっと短く記述できることがあります。
とりわけ、C++はOOPを使えるのはもちろん、言語としてサポートする予約語やテンプレートを駆使したいろんな実装方法ができますが、本当に必要なことに絞ればC言語っぽく書くとよいこともありそうです。
4. まとめ
- OOP前提で設計や実装を考える脳みそになってませんか?
- Small is Beautiful(自分に宛てたメッセージです)
Discussion