27

重複の可能性:
名前の非表示と壊れやすいベースの問題

メンバー関数の非表示に関するルールに精通しています。基本的に、基本クラス関数と同じ名前の関数を持つ派生クラスは、実際には基本クラス関数をオーバーロードしません。完全に非表示にします。

struct Base
{
    void foo(int x) const
    {

    }
};

struct Derived : public Base
{
    void foo(const std::string& s) { }
};


int main()
{
    Derived d;
    d.foo("abc");
    d.foo(123); // Will not compile! Base::foo is hidden!
}

usingしたがって、宣言でこれを回避できます。しかし、私の質問は、基本クラスの関数が非表示になる理由は何ですか?これは、標準化委員会による「機能」ですか、それとも単なる「間違い」ですか。コンパイラが一致するオーバーロードが見つからない場合に、一致するオーバーロードをBaseクラスで検索できない技術的な理由はありますd.foo(123)か?

4

4 に答える 4

23

名前ルックアップは、一致する名前の現在のスコープを調べることによって機能します。何も見つからない場合は、囲んでいるスコープを調べます。何も見つからない場合は、グローバル名前空間に到達するまで、囲んでいるスコープなどを調べます。

これはクラスに固有のものではありません。ここに隠れているのとまったく同じ名前が表示されます。

#include <iostream>

namespace outer
{
  void foo(char c) { std::cout << "outer\n"; }

  namespace inner
  {
    void foo(int i) { std::cout << "inner\n"; }

    void bar() { foo('c'); }
  }
}

int main()
{
  outer::inner::bar();
}

outer::foo(char)呼び出しfoo('c')名のルックアップは検索後に停止するouter::inner::foo(int)(つまり非表示になっている)ため、より適切に一致しますがouter::foo(char)、プログラムはを出力しinnerます。

メンバー関数名が非表示にされていない場合、クラススコープでの名前ルックアップは非クラススコープとは異なる動作をすることになり、一貫性がなく混乱を招き、C++の学習がさらに困難になります。

したがって、名前検索ルールを変更できなかった技術的な理由はありませんが、メンバー関数やその他の種類の名前検索では変更する必要があります。その後も名前の検索を続行する必要があるため、コンパイラの速度が低下します。現在のスコープで一致する名前を検索します。賢明なことに、現在のスコープに名前がある場合、それはおそらくあなたが望んでいたものです。スコープ内の呼び出しは、Aおそらくそのスコープ内の名前を検索する必要があります。たとえば、2つの関数が同じ名前空間にある場合、それらはおそらく関連しています(同じモジュールまたはライブラリの一部)。したがって、一方が他方の名前を使用する場合、それはおそらく意味します。同じスコープ内の1つを呼び出します。もしそうなら'

于 2012-08-12T17:46:21.523 に答える
10

これは、標準化委員会による「機能」ですか、それとも単なる「間違い」ですか。

規格に明記されているので間違いありません。それは機能です。

コンパイラがd.foo(123)に一致するものが見つからない場合に、一致するオーバーロードについてBaseクラスを検索できない技術的な理由はありますか?

技術的には、コンパイラは基本クラスを調べることができます。技術的に。しかし、そうすると、標準で設定されたルールに違反することになります。

しかし、私の質問は、基本クラスの関数が非表示になる理由は何ですか?

委員会の誰かが答えを出さない限り、私たちは推測することしかできないと思います。基本的に、2つのオプションがありました。

  • 派生クラスで同じ名前の関数を宣言する場合は、派生クラスから直接アクセスできる同じ名前の基本クラスの関数を保持します
  • しないでください

それはコインを投げることによって決定された可能性があります(...わかりました、多分そうではありません)。

一般に、基本クラスと同じ名前の関数が必要な理由は何ですか?さまざまな機能があります-代わりにポリモーフィズムを使用する可能性が高くなります。さまざまなケース(さまざまなパラメーター)を処理する場合、およびこれらのケースが基本クラスに存在しない場合は、戦略パターンがジョブの処理に適している可能性があります。したがって、実際に関数を非表示にしたい場合は、関数の非表示が有効になる可能性があります。基本クラスの実装に満足していないため、を使用するオプションを使用して独自の実装を提供しますが、using必要な場合に限ります。

同じ名前で異なる署名の関数を使用する前に、よく考えさせるメカニズムにすぎないと思います。

于 2012-08-12T16:54:17.407 に答える
5

@ Lol4t0はかなり正しいと思いますが、もっと強く述べたいと思います。これを許可した場合、2つの可能性があります。言語のほぼ全体にわたって他の多くの変更を行うか、またはほぼ完全に壊れたものになってしまいます。

これを機能させるために行うその他の変更は、オーバーロードの実行方法を完全に刷新することです。少なくとも、実行されたステップの順序、およびおそらくステップ自体の詳細を変更する必要があります。現在、コンパイラは名前を検索し、オーバーロードセットを形成し、オーバーロードを解決してから、選択したオーバーロードへのアクセスをチェックします。

これをうまく機能させるには、最初にアクセスをチェックするように変更し、アクセス可能な関数のみをオーバーロードセットに追加する必要があります。これにより、少なくとも@ Lol4t0の回答の例は、オーバーロードセットに追加されることはないため、コンパイルを続行できます。Base::foo

ただし、それでも、基本クラスのインターフェースに追加すると、深刻な問題が発生する可能性があることを意味します。Baseもともと含まれていなかった場合fooパブリック が追加された場合、へfooの呼び出しは突然まったく異なることを行い、(再び)それは誰が書いたのか完全に制御できなくなります。maind.foo()Derived

これを解決するには、ルールにかなり根本的な変更を加える必要があります。関数の引数の暗黙的な変換を禁止します。それに加えて、オーバーロードの解決を変更するので、同点の場合は、関数の最も派生した/最もローカルなバージョンが、あまり派生していない/外部のスコープよりも優先されました。これらのルールでは、への呼び出しはそもそも解決d.foo(5.0)できませんでした。Derived::foo(int)

ただし、それは2つの可能性しか残しません。free関数の呼び出しはメンバー関数の呼び出しとは異なるルールを持つか(free関数に対してのみ暗黙の変換が許可されます)、そうでない場合はCとのすべての互換性が完全に破棄されます(つまり、暗黙の変換も禁止されます)。すべての関数の引数で、既存のコードの膨大な量を壊してしまいます)。

要約すると、言語を完全に壊すことなくこれを変更するには、他にもかなりの数の変更を加える必要があります。そのように機能する言語を作成することはほぼ確実に可能ですが、それが完了するまでには、1つの小さな変更を加えたC ++ではなく、C++やCとはあまり似ていないまったく異なる言語になります。、または他の多くのもの。

于 2012-08-12T17:35:47.933 に答える
3

私は、この決定が物事をより単純にするために行われたことを提案することしかできません。

想像してみてください、その派生関数はベース1をオーバーロードします。次に、次のコードはコンパイルエラーを生成する必要がありますか、それともDeriveds関数を使用する必要がありますか?

struct Base
{
private:
    void foo(float);
}

struct Derived: public Base
{
public:
    void foo(int);
}

int main()
{
    Derived d;
    d.foo(5.0f);
}

オーバーロードの既存の動作によると、これはエラーを生成するはずです。

今想像してみてください、最初のバージョンBaseにはありませんでしfoo(float)た。2番目のバージョンでは表示されます。ここで、基本クラスの実現を変更すると、派生のインターフェイスが壊れます。

あなたがの開発者でDerivedあり、の開発者に影響を与えることができずBase、多くのクライアントがあなたのインターフェースを使用している場合、あなたは今悪い状況にあります。

于 2012-08-12T17:04:54.003 に答える