非constメソッドがプライベートのときにpublic constメソッドが呼び出されないのはなぜですか?


117

このコードを考えてみましょう:

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()' is private`。

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

言い換えると、なぜ過負荷解決がアクセス制御の前に来るのですか?これは奇妙です。一貫していると思いますか?コードが機能してからメソッドを追加しましたが、機能しているコードがまったくコンパイルされません。


3
C ++では、PIMPLイディオムの使用のような追加の努力なしでは、クラスの実際の「プライベート」部分はありません。これによって引き起こされる問題の1つにすぎません(「プライベート」メソッドのオーバーロードを追加し、コンパイルの古いコードを壊すことは、これをやらないことで簡単に回避できるとしても、私の本の問題として数えます)。
hyde

const関数を呼び出すことができると期待されるが、非constの対応するものがプライベートインターフェイスの一部となる実際のコードはありますか?これは、インターフェース設計が悪いように思えます。
Vincent Fourmond 2016

回答:


125

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

void foo() const

そして

void foo()

現在aはnot 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され、出力されます。これは、大規模なコードベースで追跡するのが非常に難しい場合があります。アクセス制御はオーバーロードの解決後に行われるため、呼び出したい関数を呼び出せないというコンパイラエラーが表示され、バグを簡単に見つけることができます。


アクセス制御が過負荷の解決後である理由はありますか?
drake7707

3
@ drake7707アクセス制御が最初に来た場合にコードサンプルで示すように、上記のコードはコンパイルされ、プログラムのセマンティクスが変更されます。あなたについてはわかりませんが、関数をプライベートのままにして暗黙的なキャストとコードが黙って「機能する」ようにしたい場合は、エラーが発生し、明示的なキャストを行う必要があります。
NathanOliver

「関数をプライベートにしたい場合は、明示的なキャストを行う必要があります」-ここでの本当の問題は暗黙的なキャストのように聞こえますが、一方で、派生クラスを暗黙的に使用することもできます。基本クラスはOOパラダイムの定義的な特徴ですよね。
Steven Byks

35

最終的にこれは、過負荷の解決を実行するときにアクセシビリティを考慮に入れるべきではないという規格の主張に帰着します。このアサーションは[over.match]句3にあります。

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

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

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

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

  1. オーバーロード候補のアクセシビリティを変更した結果として予期しない動作の変更が行われるのを防ぎます(代わりにコンパイルエラーが発生します)。
  2. オーバーロードの解決プロセスからコンテキスト依存を削除します(つまり、オーバーロードの解決は、クラスの内部でも外部でも同じ結果になります)。

32

過負荷を解決する前にアクセス制御が行われたとします。事実上、これは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 Xpublic(たとえば、デバッグの目的で)変更することは、上記のプログラムの意味に静かな変更がない(つまりX::a、どちらの場合もアクセスされるため、上記の例ではアクセスエラーが発生します)。public/protected/private可視性を制御する場合、プログラムの意味が変わります(グローバルaはで呼び出されprivate、それ以外の場合はX::a)。

その後、彼は、それが明示的な設計によるものか、標準C ++の前身であるClassessでCを実装するために使用されるプリプロセッサテクノロジーの副作用によるものかを思い出さないと述べています。

これはあなたの例とどのように関連していますか?基本的に、標準はオーバーロードの解決を行ったため、名前の検索はアクセス制御の前に行われるという一般的なルールに準拠しています。

10.2メンバー名のルックアップ[class.member.lookup]

1メンバー名ルックアップは、クラススコープ(3.3.7)内の名前(id-expression)の意味を決定します。名前の検索により、あいまいさが生じる可能性があり、その場合、プログラムの形式が正しくありません。id-expressionの場合、名前の検索はthisのクラススコープで始まります。修飾IDの場合、名前の検索は、nestedname-指定子のスコープから始まります。名前の検索はアクセス制御の前に行われます(3.4、条項11)。

8オーバーロードされた関数の名前が明確に見つかると、アクセス制御の前オーバーロードの解決(13.3)も行われます。あいまいさは、名前をクラス名で修飾することによって解決できることがよくあります。


23

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

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


一貫していると思いますか?私のコードは機能し、その後メソッドを追加しましたが、私の機能するコードはまったくコンパイルされません。
Narek

私はそう思います。オーバーロードの解決は意図的に煩雑です。私は昨日、同様の質問に答えました:stackoverflow.com/questions/39023325/…–
バトシェバ

5
@Narek削除された関数がオーバーロードの解決でどのように機能するかと同じように機能すると私は信じています。セットから最適なものを選択すると、それが利用できないことがわかり、コンパイラエラーが発生します。使用可能な最良の機能ではなく、最良の機能を選択して使用しようとします。
NathanOliver

3
@Narek私も最初になぜそれが機能しないのか疑問に思いましたが、これを考慮してください:非constオブジェクトにもパブリックconstを選択する必要がある場合、プライベート関数をどのように呼び出すでしょうか?
idclev 463035818

20

発生する順序を覚えておくことが重要です。

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

(3)は(2)の後に発生します。これは本当に重要です。それ以外の場合、関数をdeletedまたはdにprivateすると、意味がなくなり、推論がはるかに困難になるためです。

この場合:

  1. 実行可能な関数はA::foo()およびA::foo() constです。
  2. 実行可能な最良の関数はA::foo()、後者が暗黙のthis引数の修飾変換を含むためです。
  3. しかし、そうでA::foo()ありprivate、あなたはそれにアクセスできないので、コードの形式は正しくありません。

1
「実行可能」には関連するアクセス制限が含まれると考えるかもしれません。つまり、クラスのパブリックインターフェイスの一部ではないため、クラスの外部からプライベート関数を呼び出すことは「実行可能」ではありません。
RM

15

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

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

  1. その名前の何かがある最初の1スコープを検索します。

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

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

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

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

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


  1. 「最初」はそれ自体が少し複雑になる可能性があります。特に、テンプレートが関与する場合は、2フェーズルックアップが発生する可能性があるため、検索を開始するときに2つの完全に異なる「ルート」が開始されるためです。基本的な考え方は、けれども非常に単純です:最小の囲みスコープから開始し、ますます大きな外側のスコープに外側にあなたの方法を動作します。

1
Stroustrupは、D&Eで、このルールはCで使用されるプリプロセッサーの副作用である可能性があると推測しています。私の答えを見てください
TemplateRex

12

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


11

この呼び出しでは:

a.foo();

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

A::foo(a);

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

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

オーバーロードの解決により、最初は非const thisに選択され、2番目はに選択されますconst this。あなたが最初に削除した場合、第二の両方に結合するconstnon-const this

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

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

[class.access / 4] ...多重定義された関数名の場合、多重定義解決によって選択された関数にアクセス制御が適用されます。...

しかし、これを行うと:

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

その後、constオーバーロードのみが適合します。


それは、最適な実行可能な機能を選択するための過負荷解決の後に、アクセス制御が行われるSTRANGEです。アクセス制御は、オーバーロードの解決の前に行う必要があります。アクセス権がない場合と同様に、まったく考慮しない方がよいと思いますか。
Narek

@Narek ... C ++標準への参照を使用し私の回答を更新しました。これは、実際の方法、この動作に依存C ++で物事やイディオムがたくさんあるという意味になり
WhiZTiM

9

技術的な理由は他の回答で解決されています。私はこの質問にのみ焦点を当てます:

言い換えれば、なぜ過負荷解決がアクセス制御の前に来るのですか?これは奇妙です。一貫していると思いますか?私のコードは機能し、その後メソッドを追加しても、機能しているコードはまったくコンパイルされません。

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

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


8

関数a内の変数mainがとして宣言されていないためconstです。

定数メンバー関数は、定数オブジェクトで呼び出されます。


8

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.