122

次のコードを検討してください。

struct A
{
    void foo() const
    {
        std::cout << "const" << std::endl;
    }

    private:

        void foo()
        {
            std::cout << "non - const" << std::endl;
        }
};

int main()
{
    A a;
    a.foo();
}

コンパイラ エラーは次のとおりです。

エラー: 'void A::foo()' は非公開です。

しかし、プライベートなものを削除すると、うまくいきます。const 以外のメソッドが非公開の場合に public const メソッドが呼び出されないのはなぜですか?

言い換えれば、オーバーロードの解決がアクセス制御よりも先に行われるのはなぜでしょうか? 変ですね。一貫していると思いますか?コードが機能し、メソッドを追加すると、作業中のコードがまったくコンパイルされません。

4

11 に答える 11

126

を呼び出すa.foo();と、コンパイラはオーバーロードの解決を行って、使用するのに最適な関数を見つけます。オーバーロード セットを構築すると、

void foo() const

void foo()

現在、ais notconstであるため、非 const バージョンが最適であるため、コンパイラは を選択しvoid foo()ます。次に、アクセス制限が設定され、void foo()プライベートであるため、コンパイラ エラーが発生します。

オーバーロードの解決では、「最適な使用可能な関数を見つける」ことではないことに注意してください。「最適な機能を見つけて使ってみよう」です。アクセス制限または削除されたためにそれができない場合は、コンパイラ エラーが発生します。

言い換えれば、オーバーロードの解決がアクセス制御の前に来るのはなぜですか?

さて、見てみましょう:

struct Base
{
    void foo() { std::cout << "Base\n"; }
};

struct Derived : Base
{
    void foo() { std::cout << "Derived\n"; }
};

struct Foo
{
    void foo(Base * b) { b->foo(); }
private:
    void foo(Derived * d) { d->foo(); }
};

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

ここで、私が実際に非公開にするつもりはなかったとしましょうvoid foo(Derived * d)。アクセス制御が最初に行われた場合、このプログラムはコンパイルおよび実行され、Base印刷されます。これは、大規模なコード ベースで追跡するのが非常に難しい場合があります。アクセス制御はオーバーロードの解決後に行われるため、呼び出したい関数を呼び出すことができないという素晴らしいコンパイラ エラーが表示され、バグを簡単に見つけることができます。

于 2016-08-19T15:18:01.793 に答える
37

最終的に、これは、オーバーロードの解決を実行するときにアクセシビリティを考慮すべきではないという標準の主張に帰着します。このアサーションは、[over.match]句 3で見つけることができます。

... オーバーロードの解決が成功し、実行可能な最適な関数が、それが使用されているコンテキストでアクセスできない場合 ([class.access] 節)、プログラムの形式が正しくありません。

また、同じセクションの第 1 節の注:

[注: オーバーロードの解決によって選択された関数は、コンテキストに適しているとは限りません。関数のアクセシビリティなどのその他の制限により、呼び出しコンテキストでの使用が不適切になる可能性があります。— エンドノート]

その理由については、考えられる動機がいくつか考えられます。

  1. オーバーロード候補のアクセシビリティを変更した結果、予期しない動作の変更が発生するのを防ぎます (代わりに、コンパイル エラーが発生します)。
  2. これにより、オーバーロード解決プロセスからコンテキスト依存が取り除かれます (つまり、オーバーロード解決は、クラスの内外を問わず同じ結果になります)。
于 2016-08-19T15:39:12.570 に答える
23

暗黙のthisポインターは非constであるため、コンパイラーはまず、constバージョンの前に関数の非バージョンの存在をチェックしconstます。

non- constoneを明示的にマークするprivateと、解決は失敗し、コンパイラは検索を続行しません。

于 2016-08-19T15:05:48.900 に答える
20

発生する順序を覚えておくことが重要です。つまり、次のとおりです。

  1. 実行可能な関数をすべて見つけます。
  2. 実行可能な最適な関数を選択します。
  3. 最適な実行可能な関数が 1 つだけない場合、または実行可能な最適な関数を実際に呼び出すことができない場合 (アクセス違反または関数がdeleted になっているため) は、失敗します。

(3) は (2) の後に起こります。deleteそうしないと、関数をd またはprivateにすることは意味がなくなり、推論するのがはるかに難しくなるため、これは非常に重要です。

この場合:

  1. 実行可能な関数はA::foo()A::foo() constです。
  2. 最も実行可能な関数は、後者が暗黙の引数A::foo()の修飾変換を伴うためです。this
  3. しかし、それにアクセスできないため、コードの形式が正しくありませんA::foo()private
于 2016-08-19T15:20:28.060 に答える
15

これは、C++ でのかなり基本的な設計上の決定に帰着します。

呼び出しを満たす関数を検索するとき、コンパイラは次のような検索を実行します。

  1. その名前を持つものがある最初の1 つのスコープを見つけるために検索します。

  2. コンパイラは、そのスコープ内でその名前を持つすべての関数 (またはファンクタなど) を見つけます。

  3. 次に、コンパイラはオーバーロードの解決を行い、見つかったものの中から最適な候補を見つけます (それらがアクセス可能かどうかに関係なく)。

  4. 最後に、コンパイラは、選択された関数にアクセスできるかどうかをチェックします。

その順序のため、はい、アクセス可能な別のオーバーロードがある場合でも、コンパイラがアクセスできないオーバーロードを選択する可能性があります (ただし、オーバーロードの解決中には選択されません)。

別の方法で行うことが可能かどうかについて: はい、間違いなく可能です。ただし、C++ とはまったく異なる言語になることは間違いありません。一見マイナーな決定の多くが、最初は明らかであったよりもはるかに多くの影響を与える可能性があることが判明しました。


  1. 「最初」は、それ自体が少し複雑になる可能性があります。特に、テンプレートが関与する場合は、2 フェーズのルックアップにつながる可能性があるためです。つまり、検索を実行するときに開始する 2 つの完全に別個の「ルート」があることを意味します。ただし、基本的な考え方は非常に単純です。最小の囲みスコープから始めて、囲みスコープを徐々に大きくしていきます。
于 2016-08-19T15:20:22.500 に答える
12

アクセス制御 ( publicprotectedprivate) は、オーバーロードの解決には影響しません。void foo()最適な組み合わせであるため、コンパイラが選択します。アクセスできないという事実は変わりません。それを削除するとvoid foo() const、 のみが残ります。これが最適な (つまり、唯一の) 一致です。

于 2016-08-19T15:20:18.457 に答える
11

この呼び出しでは:

a.foo();

thisすべてのメンバー関数で使用できる暗黙のポインターが常に存在します。そして、のconst修飾はthis、呼び出し元の参照/オブジェクトから取得されます。上記の呼び出しは、コンパイラによって次のように処理されます。

A::foo(a);

しかし、次のように扱われる2 つの宣言がA::fooあります。

A::foo(A* );
A::foo(A const* );

オーバーロードの解決により、最初は non-const 用にthis選択され、2 番目は a 用に選択されますconst this。最初のものを削除すると、2 番目のものは と の両方にバインドされconstますnon-const this

実行可能な最適な機能を選択するためのオーバーロード解決の後、アクセス制御が行われます。選択したオーバーロードへのアクセスを として指定したためprivate、コンパイラは文句を言います。

標準はそう言っています:

[class.access/4] : ...オーバーロードされた関数名の場合、オーバーロード解決によって選択された関数にアクセス制御が適用されます....

しかし、これを行うと:

A a;
const A& ac = a;
ac.foo();

するとconstオーバーロードだけが収まります。

于 2016-08-19T15:12:17.583 に答える
9

技術的な理由は、他の回答で回答されています。この質問だけに焦点を当てます。

言い換えれば、オーバーロードの解決がアクセス制御の前に来るのはなぜですか? 変ですね。一貫していると思いますか?私のコードは動作しますが、メソッドを追加すると、動作するコードがまったくコンパイルされません。

それが言語の設計方法です。その意図は、可能な限り実行可能な最適なオーバーロードを呼び出そうとすることです。失敗した場合は、設計を再検討するように促すエラーがトリガーされます。

一方、コードがコンパイルされ、const呼び出されたメンバー関数でうまく機能したとします。いつか、誰か (おそらくあなた自身) が、非constメンバー関数のアクセシビリティを からprivateに変更することを決定しpublicます。その後、コンパイルエラーなしで動作が変わります! これは驚きです。

于 2016-08-19T15:35:45.947 に答える
8

アクセス指定子は、名前の検索や関数呼び出しの解決にはまったく影響しません。関数は、呼び出しがアクセス違反をトリガーするかどうかをコンパイラがチェックする前に選択されます。

このように、アクセス指定子を変更すると、既存のコードに違反がある場合にコンパイル時に警告が表示されます。関数呼び出しの解決でプライバシーが考慮された場合、プログラムの動作が静かに変化する可能性があります。

于 2016-08-19T15:24:00.390 に答える