C++ のクラスでプライベート関数を宣言する必要があるのはなぜですか? それは実際の技術的な理由(コンパイル時の役割)ですか、それとも単に一貫性のためですか?
7 に答える
プライベート関数は、他の翻訳単位が知るために何も (オブジェクト サイズも vtable エントリも) 追加しないため、なぜ宣言する必要があるのかを尋ねました。
考えてみれば、これはstatic
ファイル内でいくつかの関数を宣言することに似ています。外部からは見えませんが、コンパイラ自体にとって重要です。コンパイラは、関数を使用する前に関数のシグネチャを知りたいと考えています。そのため、最初に関数を宣言します。C++ コンパイラは 1 つのパスであることに注意してください。つまり、使用する前にすべてを宣言する必要があります。1
プログラマの観点からは、プライベート関数を宣言することはまだ完全に役に立たないというわけではありません。2 つのクラスを想像してみてください。一方は他方のクラスfriend
です。フレンドゾーン化されたクラス2は、そのクラスのプライベートがどのように見えるかを知る必要があります (この議論は奇妙になってきています)。
C++ がこのように設計された正確な理由については、最初に歴史的な理由があると言います。C では構造体をスライスできないという事実は、C++ で採用されたため、クラスをスライスできません (そして C++ で採用されました)。 C++ から分岐した他の言語も)。また、単純さについても推測します。クラスをさまざまなヘッダーファイルに分割し、ソースファイルにそれを知らせ、他の人があなたに何かを追加するのを防ぐことができるコンパイル方法を考案するのがどれほど難しいか想像してみてください。クラス。
最後の注意点として、private
関数はvtable のサイズに影響を与える可能性があります。つまり、それらがvirtual
.
1 実際には完全ではありません。クラスにインライン関数がある場合、同じクラスで後で定義された関数を参照できます。しかし、おそらくアイデアはシングルパスから始まり、この例外は後でそれに追加されました.
2 特にインライン化されたメンバー関数です。
クラス自体の定義ですべてのメンバーを宣言して、コンパイラーがどの関数がメンバーになることが許可されているかを認識できるようにする必要があります。そうしないと、2人目のプログラマーが(偶然に?)やって来てメンバーを追加したり、間違いを犯したり、オブジェクトの保証に違反したりして、未定義の動作やランダムなクラッシュを引き起こす可能性があります。
懸念事項の組み合わせがありますが、次のとおりです。
- C++ では、最初の定義後にクラスを再度開いて新しいメンバーを宣言することはできません。
- C++ では、プログラムを形成するために結合する異なる翻訳単位でクラスの異なる定義を持つことはできません。
したがって:
- .cpp ファイルがクラスで宣言したいプライベート メンバー関数は、.h ファイルで定義する必要があります。これは、クラスのすべてのユーザーにも表示されます。
実用的なバイナリ互換性の POV から: David がコメントで述べているように、プライベートvirtual
関数は、このクラスとそれをベースとして使用するすべてのクラスの vtable のサイズとレイアウトに影響します。そのため、コンパイラは、それらを呼び出すことができないコードをコンパイルする場合でも、それらについて知る必要があります。
C++ は、.cpp ファイルがクラスを再度開き、特定の種類の追加のメンバー関数を追加できるようにするために、別の方法で発明された可能性があります。これにより、バイナリ互換性が損なわれないように実装する必要がありますか? 特定の方法で異なる定義を許可するために、1 つの定義ルールを緩和できますか? たとえば、静的メンバー関数と非仮想非静的メンバー関数です。
おそらく両方にイエスです。技術的な障害はないと思いますが、現在の ODR は定義を「異なる」ものにするものについて非常に厳密です (したがって、非常に似ている定義間のバイナリ非互換性を許容する実装に対して非常に寛大です)。この種の例外をルールに導入するテキストは複雑になると思います。
最終的には「設計者がそうしたかった」ということになるかもしれませんし、誰かが試してみて思いもよらなかった障害に遭遇したのかもしれません。
アクセス レベルは可視性に影響しません。プライベート関数は外部コードに表示され、オーバーロードの解決によって選択される場合があります (アクセス違反エラーが発生します)。
class A {
void F(int i) {}
public:
void F(unsigned i) {}
};
int main() {
A a;
a.F(1); // error, void A::F(int) is private
}
これが機能するときの混乱を想像してください。
class A {
public:
void F(unsigned i) {}
};
int main() {
A a;
a.F(1);
}
// add private F overload to A
void A::F(int i) {}
しかし、それを最初のコードに変更すると、オーバーロードの解決によって別の関数が選択されます。また、次の例はどうでしょうか。
class A {
public:
void F(unsigned i) {}
};
// add private F overload to A
void A::F(int i) {}
int main() {
A a;
a.F(1);
}
または、これがうまくいかない別の例を次に示します。
// A.h
class A {
public:
void g() { f(1); }
void f(unsigned);
};
// A_private_interface.h
class A;
void A::f(int);
// A.cpp
#include "A_private_interface.h"
#include "A.h"
void A::f(int) {}
void A::f(unsigned) {}
// main.cpp
#include "A.h"
int main() {
A().g();
}
理由の 1 つは、C++ では友人があなたのプライベートにアクセスできることです。友達がそれらにアクセスするには、友達がそれらについて知っている必要があります。
プライベート関数を宣言する必要がある理由はいくつかあります。
最初のコンパイル時のエラー チェック
アクセス修飾子のポイントは、コンパイル時にプログラミング エラーの特定のクラス (しゃれは意図されていません) をキャッチすることです。プライベート関数は、誰かがクラス外から呼び出すとバグになる関数であり、できるだけ早くそれについて知りたいと考えています。
2 番目のキャスティングと継承
C++ 標準から取得:
3 [ 注: プライベート基本クラスのメンバーは、継承されたメンバー名としてアクセスできない場合がありますが、直接アクセスできます。ポインター変換 (4.10) および明示的なキャスト (5.4) に関する規則により、派生クラスへのポインターからアクセスできない基本クラスへのポインターへの変換は、暗黙的な変換が使用されている場合、整形式ではない可能性がありますが、整形式です。明示的なキャストが使用されている場合。
3人目の友達
友達同士がプライベートで見せ合います。プライベート メソッドは、フレンドである別のクラスから呼び出すことができます。
第 4 回 一般的な正気と優れた設計
別の 100 人の開発者と一緒にプロジェクトに取り組んだことがあります。標準と一般的なルールのセットを用意することで、保守性を維持できます。何かをプライベートに宣言することは、グループ内の他のすべての人にとって特定の意味を持ちます。
また、これは優れたオブジェクト指向設計の原則にもつながります。何を公開し、何を公開しないか
クラスのプライベート メンバーは引き続きクラスのメンバーであるため、他のパブリック メンバーの実装がそのプライベート メソッドに依存する可能性があるため、宣言する必要があります。それらを宣言すると、コンパイラはその関数への呼び出しをメンバー関数呼び出しとして理解できるようになります。
ファイル内でのみ使用され.cpp
、クラスの他のプライベート メンバーへの直接アクセスに依存しないメソッドがある場合は、それを匿名名前空間に移動することを検討してください。その後、ヘッダー ファイルで宣言する必要はありません。