web-dev-qa-db-ja.com

非constメソッドがprivateであるときに、パブリックconstメソッドが呼び出されないのはなぜですか?

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

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メソッドが呼び出されないのはなぜですか?

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

113
Narek

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

_void foo() const
_

そして

_void foo()
_

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

126
NathanOliver

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

...オーバーロード解決が成功し、使用されるコンテキストで最適な実行可能関数にアクセスできない(Clause [class.access])場合、プログラムは不正な形式です。

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

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

なぜかというと、考えられる動機はいくつか考えられます。

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

オーバーロード解決の前にアクセス制御が行われたとします。事実上、これは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は、現在のルール(アクセシビリティの前の可視性)の利点は(一時的に)privateclass X into public(例:デバッグ目的)は、上記のプログラムの意味に静かな変化がないことです(つまり、X::aは両方の場合にアクセスされようとしますが、上記の例ではアクセスエラーが発生します。 public/protected/privateは可視性を制御し、プログラムの意味は変わります(グローバルaprivateで呼び出され、そうでなければ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)も行われます。多くの場合、あいまいさは、名前をクラス名で修飾することで解決できます。

31
TemplateRex

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

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

23
Bathsheba

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

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

(3)(2)の後に発生します。そうでなければ、deletedまたはprivateのような関数を作成すると意味がなくなり、推論するのがはるかに難しくなります。

この場合:

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

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

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

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

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

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

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

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

それがpossibleであるかどうかについては、物事を異なる方法で行うことです。はい、それは間違いなく可能です。ただし、C++とはまったく異なる言語になることは間違いありません。一見些細な決定の多くは、最初に明らかになるよりもはるかに多くの影響を与えることがあります。


  1. 「最初」は、特にテンプレートが関与する場合に、それ自体が少し複雑になる可能性があります。これは、2段階のルックアップにつながる可能性があるためです。 basicという考え方は非常に単純です。つまり、最も小さい囲みスコープから開始し、外側に向かってより大きな囲みスコープに向かって進みます。
14
Jerry Coffin

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

12
Pete Becker

この呼び出しでは:

a.foo();

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

A::foo(a);

しかし、A::fooの2つの宣言があり、これは同様に扱われますです。

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

オーバーロード解決により、最初は非定数thisに対して選択され、2番目はconst thisに対して選択されます。前者を削除すると、後者はconstnon-constthisの両方にバインドされます。

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

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

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

しかし、これを行う場合:

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

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

11
WhiZTiM

技術的な理由は他の答えで答えられています。この質問にのみ焦点を当てます:

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

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

一方、コードがコンパイルされ、呼び出されるconstメンバー関数で適切に機能するとします。いつか、誰か(おそらく自分)が非constメンバー関数のアクセシビリティをprivateからpublicに変更することにしました。次に、コンパイルエラーなしで動作が変更されます。これはサプライズになります。

9
songyuanyao

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

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

8
Kyle Strand

a関数の変数mainconstとして宣言されていないためです。

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