質問の文言(「隠す」という言葉を使用した)から判断すると、ここで何が行われているのかはすでにわかっています。この現象は「名前の非表示」と呼ばれます。何らかの理由で、名前の非表示が発生する理由について誰かが質問するたびに、応答する人は、これが「名前の非表示」と呼ばれ、それがどのように機能するかを説明します(おそらくすでに知っています)、またはそれを上書きする方法を説明します(質問されたことはありません)が、実際の「なぜ」の質問に対処する気がないようです。
名前非表示の背後にある根拠、つまり実際にC ++に設計された理由は、オーバーロードされた関数の継承されたセットが現在のセットと混合することが許可された場合に発生する可能性のある、直感的で予期せぬ危険な動作を回避することです。指定されたクラスのオーバーロード。C ++では、候補のセットから最適な関数を選択することでオーバーロードの解決が機能することをご存じでしょう。これは、引数のタイプをパラメーターのタイプに一致させることによって行われます。一致ルールは時々複雑になる可能性があり、多くの場合、準備されていないユーザーによって非論理的であると認識される可能性のある結果につながります。既存の関数のセットに新しい関数を追加すると、オーバーロードの解決結果が大幅に変化する可能性があります。
たとえば、基本クラスにタイプのパラメーターを取るB
メンバー関数foo
がありvoid *
、へのすべての呼び出しfoo(NULL)
がに解決されるとしましょうB::foo(void *)
。名前の非B::foo(void *)
表示はなく、これはから派生した多くの異なるクラスで表示されるとしましょうB
。ただし、D
クラスの一部の[間接、リモート]子孫でB
関数foo(int)
が定義されているとしましょう。今、名前の隠蔽せずにD
両方持っているfoo(void *)
とfoo(int)
見えるとオーバーロードの解決に参加します。foo(NULL)
タイプのオブジェクトを介して行われる場合、どの関数が解決するために呼び出しD
ますか?彼らはに解決されD::foo(int)
ているので、int
整数ゼロのためのより良いマッチである(つまり、NULL
)どのポインタ型よりも。したがって、階層全体でfoo(NULL)
1つの関数に解決するように呼び出しが行われ、その中D
(およびその下)で突然別の関数に解決されます。
別の例は、C ++の設計と進化、 77ページにあります。
class Base {
int x;
public:
virtual void copy(Base* p) { x = p-> x; }
};
class Derived{
int xx;
public:
virtual void copy(Derived* p) { xx = p->xx; Base::copy(p); }
};
void f(Base a, Derived b)
{
a.copy(&b); // ok: copy Base part of b
b.copy(&a); // error: copy(Base*) is hidden by copy(Derived*)
}
このルールがなければ、bの状態は部分的に更新され、スライスにつながります。
言語の設計時には、この動作は望ましくないと見なされていました。より良いアプローチとして、「名前非表示」の仕様に従うことが決定されました。つまり、各クラスは、宣言する各メソッド名に関して「クリーンシート」で始まります。この動作をオーバーライドするには、ユーザーからの明示的なアクションが必要です。最初は継承されたメソッドの再宣言(現在は非推奨)ですが、現在はusing宣言を明示的に使用しています。
元の投稿で正しく観察したように(私は「ポリモーフィックではない」という発言を参照しています)、この動作はクラス間のIS-Aリレーションシップの違反と見なされる可能性があります。これは本当ですが、どうやら当時、名前を隠すことはそれほど悪ではないことが判明することが判明しました。