プライベート変数は、複雑さと実装の詳細をクラスのユーザーに隠す方法です。これはかなりいい機能です。しかし、c ++でそれらをクラスのヘッダーに配置する必要がある理由がわかりません。これには2つの迷惑な欠点があります。
この要件の背後に概念的な理由はありますか?コンパイラーからの作業を容易にするためだけですか?
これは、インスタンス化時に適切な量のメモリを割り当てるために、C++コンパイラがクラスの実際のサイズを知っている必要があるためです。サイズにはすべてのメンバーが含まれ、プライベートメンバーも含まれます。
これを回避する1つの方法は、Pimplイディオムを使用することです。これは、Herb Sutterが彼のGuru of the Weekシリーズ #24 で説明しています。 #28 。
実際、これ(より一般的には、ヘッダー/ソースファイルの区別と#include
s)は、Cから継承されたC++の主要なハードルです。 C++ Cが作成されましたが、大規模なソフトウェア開発の経験はまだありませんでした。それ以来学んだ教訓は新しい言語の設計者に注意を払われましたが、C++には下位互換性の要件があるため、言語のこのような根本的な問題に対処するのは非常に困難です。
クラス定義は、クラスのオブジェクトを使用した場所にかかわらず、コンパイラーがメモリー内に同じレイアウトを生成するのに十分である必要があります。たとえば、次のようなものが与えられたとします。
class X {
int a;
public:
int b;
};
コンパイラーは通常、オフセット0にa
、オフセット4
にb
を持ちます。コンパイラがこれを単に見た場合:
class X {
public:
int b;
};
b
がオフセット4ではなくオフセット0にあると "考え"ます。その定義を使用してコードをb
に割り当てた場合、最初の定義を使用したコードはa
getになります。変更、およびその逆。
クラスのプライベート部分に変更を加えることの影響を最小限に抑える通常の方法は、通常、pimplイディオムと呼ばれます(これについては、Googleが大量の情報を提供できると確信しています)。
いくつかの理由が考えられます。プライベートメンバーは他のほとんどのクラスからはアクセスできませんが、フレンドクラスからはアクセスできます。したがって、少なくともこの場合、それらはヘッダーで必要になる可能性があるため、friendクラスはそれらが存在することを確認できます。
依存ファイルの再コンパイルは、インクルード構造によって異なる場合があります。別のヘッダーの代わりに.cppファイルに.hファイルを含めると、再コンパイルの長いチェーンを防ぐことができる場合があります。
これが必要な主な理由は、クラスを使用するコードは、それを処理できるコードを生成するために、プライベートクラスメンバーについて知る必要があるためです。
次のクラスについて考えてみます。
_//foo.h
class foo {
char private_member[0x100];
public:
void do_something();
};
_
これは次のコードで使用されます。
_#include "foo.h"
void f() {
foo x;
x.do_something();
}
_
このコードを32ビットLinuxでgccを使用してコンパイルし、分析を簡略化するためにいくつかのフラグを指定すると、関数f
は(コメント付きで)コンパイルされます。
_;allocate 256 bytes on the stack for a foo, plus 4 bytes for a foo*
0: 81 ec 04 01 00 00 sub esp,0x104
;the trivial constructor for foo is elided here
;compute the address of x
6: 8d 44 24 04 lea eax,[esp+0x4]
;pass the foo* to the function being called (the implicit first argument, this)
a: 89 04 24 mov DWORD PTR [esp],eax
;call x.do_something()
d: e8 fc ff ff ff call e <_Z1fv+0xe>
;deallocate the stack space used for this function
12: 81 c4 04 01 00 00 add esp,0x104
;return
18: c3 ret
_
ここで注目すべき点が2つあります。
sizeof(foo)
を知る必要があります。基本的に、プログラマーはクラスを使用するためにその実装について知っている必要はありませんが、コンパイラーは知っています。 C++設計者は、いくつかのレベルの間接参照を導入することにより、プライベートクラスメンバーがクライアントコードに認識されないようにすることができましたが、場合によっては、パフォーマンスに深刻な影響を与える可能性があります。代わりに、プログラマは、トレードオフに価値があると判断した場合、この間接指定を自分で実装することを決定できます(たとえば pImpl idiom を使用)。
根本的な問題は、C++のヘッダーファイルにコンパイラが必要とする情報が含まれている必要があることですが、クラスの人間のユーザーのための参照としても使用されます。
クラスの人間のユーザーとして、私は多くのことを気にしません。プライベートフィールドは1つで、関数とメソッドのインライン実装は別のものです。一方、コンパイラーは多くのことを気にします。
Swiftのようなより最近の言語では、少しCPU時間を使ってこれを解決します。永続的に保存されるファイルは1つだけであり、それがソースファイルです。コンパイラは、クラスを使用して他のソースファイルをコンパイルするために必要なすべてのものです。このアイデアにより、C++ではヘッダーファイルとなるものを人間のユーザーに示すことができ、ユーザーが望むものだけが含まれます。つまり、プライベートメソッドではなく、すべてのコメントが含まれます型、定数、メソッドなどが存在します。人間のユーザーがまさに望んでいるものです(Xcode IDEオプションで、メソッドが完全に異なる言語から呼び出される方法をオプションで示します)。