次のコードを検討してください。
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メソッドがprivateであるときに、パブリックconstメソッドが呼び出されないのはなぜですか?
言い換えれば、なぜオーバーロード解決はアクセス制御の前に来るのですか?これはおかしい。一貫していると思いますか?私のコードは動作し、メソッドを追加しますが、動作中のコードはまったくコンパイルされません。
a.foo();
を呼び出すと、コンパイラはオーバーロード解決を実行して、使用する最適な関数を見つけます。オーバーロードセットを構築するとき、それは見つけます
_void foo() const
_
そして
_void foo()
_
現在、a
はconst
ではないため、非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
が出力されます。これは、大規模なコードベースで追跡するのが非常に難しい場合があります。アクセス制御はオーバーロードの解決後に行われるため、呼び出したい関数が呼び出せないというナイスコンパイラエラーが発生し、バグを簡単に見つけることができます。
最終的に、これは、オーバーロード解決を実行する際にアクセシビリティを考慮すべきでないという規格の主張に帰着します。このアサーションは [over.match] 句3にあります。
...オーバーロード解決が成功し、使用されるコンテキストで最適な実行可能関数にアクセスできない(Clause [class.access])場合、プログラムは不正な形式です。
また、同じセクションの条項1の注:
[注:オーバーロード解決によって選択された関数は、コンテキストに適しているとは限りません。関数のアクセシビリティなど、その他の制限により、呼び出し側コンテキストでの使用が不正な形式になる可能性があります。 —終了ノート]
なぜかというと、考えられる動機はいくつか考えられます。
オーバーロード解決の前にアクセス制御が行われたとします。事実上、これはpublic/protected/private
アクセシビリティではなく可視性を制御しました。
StroustrupによるC++の設計と進化 のセクション2.10には、次の例について説明する箇所があります。
int a; // global a
class X {
private:
int a; // member X::a
};
class XX : public X {
void f() { a = 1; } // which a?
};
Stroustrupは、現在のルール(アクセシビリティの前の可視性)の利点は(一時的に)private
をclass X
into public
(例:デバッグ目的)は、上記のプログラムの意味に静かな変化がないことです(つまり、X::a
は両方の場合にアクセスされようとしますが、上記の例ではアクセスエラーが発生します。 public/protected/private
は可視性を制御し、プログラムの意味は変わります(グローバルa
はprivate
で呼び出され、そうでなければX::a
)。
その後、彼は、明示的な設計によるものか、Cを標準C++の前身とするCを実装するために使用されたプリプロセッサテクノロジーの副作用によるものかを思い出さないと述べています。
これはあなたの例にどのように関連していますか?基本的に、標準ではオーバーロードの解決が、アクセス制御の前に名前の検索が行われるという一般的な規則に準拠しているためです。
10.2メンバー名検索[class.member.lookup]
1メンバー名のルックアップは、クラススコープ(3.3.7)の名前(id-expression)の意味を決定します。名前の検索はあいまいさをもたらす可能性があり、その場合、プログラムの形式は正しくありません。 id-expressionの場合、名前の検索はこのクラススコープで開始されます。修飾IDの場合、ネストされた名前指定子のスコープで名前の検索が開始されます。 アクセス制御の前に名前の検索が行われます(3.4、条項11)。
8オーバーロードされた関数の名前が明確に見つかった場合、アクセス制御の前にオーバーロード解決(13.3)も行われます。多くの場合、あいまいさは、名前をクラス名で修飾することで解決できます。
暗黙のthis
ポインターは非const
であるため、コンパイラーは最初にconst
の前に関数の非const
バージョンの存在をチェックします版。
Non _const
one private
を明示的にマークすると、解決は失敗し、コンパイラは検索を続行しません。
起こることの順序を覚えておくことが重要です。
delete
dであるため)実際に最適な実行可能な関数を呼び出せない場合、失敗します。(3)(2)の後に発生します。そうでなければ、delete
dまたはprivate
のような関数を作成すると意味がなくなり、推論するのがはるかに難しくなります。
この場合:
A::foo()
およびA::foo() const
です。A::foo()
です。後者は、暗黙のthis
引数の修飾変換を伴うためです。A::foo()
はprivate
であり、アクセスできないため、コードの形式が正しくありません。これは、C++でのかなり基本的な設計決定に帰着します。
呼び出しを満たす関数を検索すると、コンパイラは次のような検索を実行します。
最初を見つけるために検索します1 その名前のsomethingがあるスコープ。
コンパイラは、そのスコープ内でその名前を持つ関数(またはファンクタなど)をallで見つけます。
その後、コンパイラーはオーバーロード解決を行い、見つかった候補の中から最適な候補を見つけます(アクセス可能かどうかに関係なく)。
最後に、コンパイラはその選択された関数がアクセス可能かどうかをチェックします。
その順序のために、はい、アクセス可能な別のオーバーロードがある場合でも、コンパイラがアクセスできないオーバーロードを選択する可能性があります(ただし、オーバーロード解決中に選択されません)。
それがpossibleであるかどうかについては、物事を異なる方法で行うことです。はい、それは間違いなく可能です。ただし、C++とはまったく異なる言語になることは間違いありません。一見些細な決定の多くは、最初に明らかになるよりもはるかに多くの影響を与えることがあります。
アクセス制御(public
、protected
、private
)は、オーバーロードの解決に影響しません。コンパイラはvoid foo()
を選択します。これは最適な一致だからです。アクセスできないという事実はそれを変えません。削除するとvoid foo() const
のみが残り、これが最適な(つまり、唯一の)一致となります。
この呼び出しでは:
a.foo();
すべてのメンバー関数で使用可能な暗黙的なthis
ポインターが常にあります。 const
のthis
修飾は、呼び出し元の参照/オブジェクトから取得されます。上記の呼び出し処理は、コンパイラによって次のように扱われます。
A::foo(a);
しかし、A::foo
の2つの宣言があり、これは同様に扱われますです。
A::foo(A* );
A::foo(A const* );
オーバーロード解決により、最初は非定数this
に対して選択され、2番目はconst this
に対して選択されます。前者を削除すると、後者はconst
とnon-const
this
の両方にバインドされます。
最適な実行可能な機能を選択するためのオーバーロード解決の後、アクセス制御が行われます。選択したオーバーロードへのアクセスをprivate
として指定したため、コンパイラーは文句を言います。
標準はそう言っています:
[class.access/4] :...オーバーロードされた関数名の場合、アクセス制御はオーバーロード解決によって選択された機能....
しかし、これを行う場合:
A a;
const A& ac = a;
ac.foo();
次に、const
オーバーロードのみが適合します。
技術的な理由は他の答えで答えられています。この質問にのみ焦点を当てます:
言い換えれば、なぜアクセス制御の前にオーバーロード解決が必要なのでしょうか?これはおかしい。一貫していると思いますか?私のコードは機能しますが、メソッドを追加すると、動作するコードはまったくコンパイルされません。
それが言語の設計方法です。意図は、可能な限り最高の実行可能なオーバーロードを呼び出すことです。失敗すると、エラーがトリガーされ、設計を再度検討するように通知されます。
一方、コードがコンパイルされ、呼び出されるconst
メンバー関数で適切に機能するとします。いつか、誰か(おそらく自分)が非const
メンバー関数のアクセシビリティをprivate
からpublic
に変更することにしました。次に、コンパイルエラーなしで動作が変更されます。これはサプライズになります。
アクセス指定子は、名前の検索と関数呼び出しの解決に影響を与えません。関数は、呼び出しがアクセス違反をトリガーするかどうかをコンパイラがチェックする前に選択されます。
このように、アクセス指定子を変更すると、既存のコードに違反がある場合、コンパイル時に警告されます。関数呼び出しの解決のためにプライバシーが考慮されると、プログラムの動作が静かに変化する可能性があります。
a
関数の変数main
はconst
として宣言されていないためです。
定数メンバー関数は、定数オブジェクトで呼び出されます。