なぜプライベートメンバー関数をヘッダーに入れるのですか?


16

プライベートメンバー変数をC ++ヘッダーに配置する理由の答えは、コンパイラがスタック内を適切に移動するコードを生成できるように、インスタンスが宣言されたポイントでクラスのサイズを知る必要があるということです。

プライベートメンバーをヘッダーに配置する必要があるのはなぜですか?

しかし、クラス定義でプライベート関数を宣言する理由はありますか?

代替案は、本質的にはくぼみのイディオムですが、余分な間接化はありません。

この言語機能は歴史的なエラー以上のものですか?

回答:


11

プライベートメンバー関数はでありvirtual、C ++(vtableを使用する)の一般的な実装では、クラスのすべてのクライアントが仮想関数の特定の順序と数を知る必要があります。これは、1つ以上の仮想メンバー関数がであっても適用されますprivate

コンパイラーの実装の選択は言語仕様に影響を与えるべきではないため、これは「馬の前にカートを置く」ように見えるかもしれません。ただし、実際には、C ++言語自体は、vtablesを使用する実際の実装(Cfront)と同時に開発されました。


4
「実装の選択は言語に影響を与えるべきではありません」は、C ++のマントラの正反対です。この仕様は特定の実装を強制するものではありませんが、特に効率的な実装選択の要件を満たすために特別に作成された多くのルールがあります。
ベンフォイト

これは非常にもっともらしい。これに関する情報源はありますか?
プラキソリック

@Praxeolitic:Virtual method tableについて読むことができます
グレッグヒューギル

1
@Praxeolitic-「The Annotated C ++ Refernce Manual」(stroustrup.com/arm.html)の古いコピーを見つけてください- 著者は、さまざまな実装の詳細と、それが言語をどのように形成したかについて話します。
マイケルコーン

これが本当に質問に答えるかどうかはわかりません。私にはそれだけのアドレス1つの特定のファセット-とはいえ十分-と残りの後ろの葉は:なぜ我々は、配置する必要があります非仮想ヘッダにプライベートメンバ関数を?確かに、純粋に一貫性/単純さのために1つの議論があります-しかし、理想的には、機械的および/または哲学的根拠もあります。そのため、これは非常に有益な効果を持つ非常に慎重な設計の選択として説明していると思う答えを追加しました。
underscore_d

13

定義以外のクラスにメソッドを追加できるようにした場合、任意のファイルの任意の場所に追加できます。

これにより、すべてのクライアントコードが、プライベートデータおよび保護されたデータメンバーに簡単にアクセスできるようになります。

クラスの定義が完了すると、一部のファイルを作成者によって特別に祝福されているとマークして、それを拡張する方法はありません。フラットな翻訳単位があります。そのため、特定のメソッドセットが公式であるか、クラス作成者によって祝福されていることをコンパイラに伝える唯一の合理的な方法は、クラス内で宣言することです。


C ++のメモリに直接アクセスできることに注意してください。つまり、通常、クラスと同じメモリレイアウトでシャドウタイプを作成し、独自のメソッドを追加する(またはすべてのデータをパブリックにする)ことは簡単reinterpret_castです。または、私はあなたのプライベート関数のコードを見つけるか、それを逆アセンブルすることができます。または、シンボルテーブルで関数アドレスを検索し、または直接呼び出します。

これらのアクセス指定子は、これらの攻撃を防ぐことはできません。それは不可能だからです。クラスの使用方法のみを示します。


1
クラス定義は、クライアントコードから隠された関数コンテナエンティティを参照できます。あるいは、これを逆にして、クライアントコードから隠された完全な従来のクラス定義は、パブリックメンバーのみを記述し、そのサイズを明らかにできる可視のインターフェースを指定できます。
プラクセオリック

1
確かに、コンパイルモデルは、クライアントコードが独自のバージョンの隠しコンテナーまたは別のアクセサーを含む別の表示インターフェイスを挿入するのを停止する合理的な方法を提供しません。
役に立たない

それは良い点ですが、とにかくヘッダーにないすべてのメンバー関数の定義には当てはまりませんか?
プラクセオリック

1
ただし、すべてのメンバー関数はクラス内で宣言されます。ポイントは、少なくともクラス定義で宣言されていない新しいメンバー関数を追加できないことです(そして、1つの定義ルールは複数の定義を防ぎます)。
役に立たない

それでは、プライベート関数ルールの1つのコンテナはどうでしょうか?
プラクセオリック

8

受け入れられた答えは、仮想プライベート機能についてこれを説明していますが、それは質問の1つの特定の側面だけに答えます。それはOPが尋ねたものよりかなり制限されています。したがって、言い換える必要があります:なぜ非仮想プライベート関数をヘッダーで宣言する必要があるのですか?

別の答えは、クラスを1つのブロックで宣言する必要があるという事実を呼び出します。その後、クラスは封印されて追加できません。それは、ヘッダーでプライベートメソッドを宣言することを省略し、それを他の場所で定義しようとすることによって行うことです。ナイスポイント。クラスの一部のユーザーが他のユーザーが観察できない方法でそれを増強できるのはなぜですか?プライベートメソッドはその一部であり、これから除外されません。しかし、その後、なぜそれらが含まれているのを尋ねると、少しトートロジカルに見えます。クラスのユーザーがそれらについて知っている必要があるのはなぜですか?それらが表示されていない場合、ユーザーは追加できませんでした。

そのため、デフォルトでプライベートメソッドを含めるだけでなく、ユーザーに見えるようにするための特定のポイントを提供する答えを提供したかったのです。パブリック宣言を必要とする非仮想プライベート関数の機構的理由は、その根拠の一部としてのPimplイディオムについてのHerb SutterのGotW#100に記載されています。私たちは皆、それについて知っていると確信しているので、ここではPimplについては説明しません。ただし、関連するビットは次のとおりです。

C ++では、ヘッダーファイルのクラス定義のいずれかが変更された場合、そのクラスのすべてのユーザーを再コンパイルする必要があります。クラスのユーザーがアクセスすることさえできないプライベートクラスメンバだけが変更された場合でも。これは、C ++のビルドモデルがテキストのインクルードに基づいているため、およびC ++が、呼び出し元がプライベートメンバーによって影響を受ける可能性のあるクラスに関する2つの主なことを知っていると想定しているためです。

  • サイズとレイアウト:[メンバーと仮想関数の-自明であり、パフォーマンスに優れていますが、ここにいる理由ではありません]
  • 関数:呼び出しコードは、非プライベート関数でオーバーロードするアクセスできないプライベート関数を含む、クラスのメンバー関数への呼び出しを解決できる必要があります。プライベート関数がより一致する場合、呼び出しコードはコンパイルに失敗します。(C ++は、安全上の理由からアクセシビリティチェックの前にオーバーロード解決を実行するように意図的な設計決定を行いました。たとえば、関数のアクセシビリティをプライベートからパブリックに変更しても、正当な呼び出しコードの意味は変わらないと考えられました。)

もちろん、Sutterは委員会のメンバーとして非常に信頼できる情報源であるため、彼は「意図的な設計上の決定」を知っています。そして、セマンティクスの変更や後で誤ってアクセス可能性が損なわれることを回避する方法として、プライベートメソッドのパブリック宣言を要求するという考えは、おそらく最も説得力のある理由です。ありがたいことに、以前は全体がかなり無意味に見えたので!


しかし、あなたはその理由を尋ねます。その答えはトートロジカルに見えます。再び、なぜクラスのユーザーは彼らについて知る必要があるのですか?」いいえ、それはトートロジカルではありません。ユーザーは必ずしも「それらについて知る必要はありません」。起こる必要があるのは、クラス作成者の同意なしに人々がクラスを拡張するのを防ぐことができる必要があるということです。したがって、そのようなメンバーはすべて事前に宣言する必要があります。これによりユーザーがプライベートメンバーについて知るようになるのは、単に必要な結果です。
ニコルボラス

1
@NicolBolas確かに。それはおそらく私の側ではあまり良い言い回しではなかったでしょう。答えは、プライベートメソッドの可視性に関する理論的根拠を提供するのではなく、多くのことをカバーする(非常に有効な)ルールの結果としてプライベートメソッドの可視性のみを説明することを意味しました。もちろん、本当の答えは、役に立たないもの、鼻切りなもの、私のものの組み合わせです。まだここになかった別の視点を提供したいだけです。
underscore_d

4

これには2つの理由があります。

まず、アクセス指定子がコンパイラ用であり、実行時には関係ないことを理解してください。スコープ外のプライベートメンバーにアクセスすると、コンパイルエラーになります。

簡潔

短い、1行または2行の関数を考えます。他の場所でのコードの複製を減らすために存在します。これは、多くの代わりに1つの場所でアルゴリズムまたはその他の動作を変更できるという利点もあります(ソートアルゴリズムの変更など)。

ヘッダーに1行または2行の短い行を挿入するか、そこに関数のプロトタイプと実装を追加しますか?ヘッダーで簡単に見つけることができます。また、短い関数の場合、個別の実装を使用する方がはるかに冗長です。

もう1つの大きな利点があります。

インライン関数

プライベート関数はインライン化できる場合があり、これには必然的にヘッダー内にあることが必要です。このことを考慮:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

プライベート関数、パブリック関数とともにインライン化できる場合があります。inlineキーワードは技術的には提案であり、要件ではないため、これはコンパイラの裁量で行われます。


クラス本体内で定義されたすべての関数は自動的にマークさinlineれます。そこでキーワードを使用する理由はありません。
ベンフォイト

@BenVoigtそうです。私はそれを理解していませんでしたが、かなり長い間C ++のパートタイムを使用しています。その言語は、そのような小さなナゲットで私を驚かせることは決してありません。

メソッドをインライン化するためにヘッダーに含める必要はないと思います。同じコンパイル単位にある必要があります。クラスごとに別々のコンパイル単位を持つことが理にかなっている場合はありますか?
サミュエルダニエルソン

@SamuelDanielsonは正しいですが、プライベート関数はクラス定義に含まれている必要があります。「プライベート」という概念は、それがクラスの一部であることを意味します。.cppクラス定義の外部で定義されているメンバー関数によってインライン化されている非メンバー関数をファイルに含めることは可能ですが、そのような関数はプライベートではありません。

質問の本文は、「クラス定義でプライベート関数を宣言する理由はありますか(これは、コンテキストがOPが実際にクラス宣言を意味することを示しているため)」です。あなたはプライベート関数の定義について話している。これは質問に答えません。@SamuelDanielson現在、LTOは、関数がプロジェクト内のどこにでもあり、インライン化される機会を平等に保持できることを意味します。クラスを複数の翻訳単位に分割する場合、最も単純なケースは、クラスが非常に大きく、意味的に複数のソースファイルに分割することです。私はこのような大規模なクラスが落胆していることを確認んだけど、とにかく
underscore_d

2

ヘッダーファイルにプライベートメソッドを含めるもう1つの理由:パブリックインラインメソッドは、1つまたは複数のプライベートメソッドを呼び出すだけではない場合があります。ヘッダーにプライベートメソッドがあることは、パブリックメソッドの呼び出しをプライベートメソッドの実際のコードに対して完全にインライン化できることを意味し、インライン化はプライベートメソッドの呼び出しで停止しません。別のコンパイルユニットからでも(およびパブリックメソッドは通常、異なるコンパイルユニットから呼び出されます)。

もちろん、プライベートメソッドを含むすべてのメソッドを知らない場合、コンパイラがオーバーロード解決の問題を検出できないという理由もあります。


インライン化の優れた点!これは、stdlibと、インラインで定義されたメソッドを持つ他のライブラリに特に関連があると思います。(そうでない場合はそんなにLTOは、TUの境界を越えて、ほとんど何でもできる内部コード、用)
underscore_d

0

これらの関数がプライベートメンバーにアクセスできるようにするためです。そうでなければfriend、とにかくヘッダーでそれらをする必要があります。

関数がクラスのプライベートメンバーにアクセスできる場合、プライベートは役に立ちません。


1
質問でもっと明確にすべきだったかもしれません-私は言語がこのように設計されている理由を意味しましたか?なぜそれが必要なのか、なぜこのデザインが良いデザインなのか?
プラクセオリック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.