13

C++98/C++03 標準および C++0x の将来の標準の、仮想継承における優位性に関する正確な規則は何ですか?

特定の段落だけを求めているわけではありませんが、それも求めています (セクション 10 のどこかだと思います)。

私は標準語の結果についても尋ねています、標準語は明確に説明しました。

4

1 に答える 1

25

これがあなたが探している言語だと思います。C++03 ISO 仕様の §10.2/2 には、次の内容があります。

次の手順では、クラス スコープ C での名前検索の結果を定義します。最初に、クラスとその基本クラスのサブオブジェクトのそれぞれでの名前のすべての宣言が考慮されます。サブオブジェクト B のメンバー名 f は、A が B の基本クラス サブオブジェクトである場合、サブオブジェクト A のメンバー名 f を隠します。そのように隠されている宣言は考慮から除外されます。using 宣言によって導入されたこれらの各宣言は、using 宣言によって指定された宣言を含む型である C の各サブオブジェクトからのものであると見なされます。結果の宣言セットがすべて同じ型のサブオブジェクトからのものではない場合、またはセットに非静的メンバーがあり、別個のサブオブジェクトからのメンバーが含まれている場合、あいまいさがあり、プログラムの形式が正しくありません。それ以外の場合、そのセットはルックアップの結果です。

大まかに言えば、これは、名前を検索しようとすると、すべての基本クラスとクラス自体を検索して、その名前の宣言を見つけることを意味します。次に、クラスごとに移動し、それらの基本オブジェクトの 1 つにその名前のものがあれば、そのオブジェクトの基本クラスのいずれかに導入されたすべての名前を非表示にします。

ここで重要な詳細は次の行です。

非常に隠されている宣言は、考慮から除外されます。

重要なことに、これは何かによって何かが隠されている場合、隠されていると見なされて削除されることを示しています。したがって、たとえば、これを行うと:

                            class D {
                            public:
                                void f();
                            }

   class B: virtual public D {        class C: virtual public D {
   public:                            public:
        void f();                         /* empty */
   };                                 };

                       class A: public B, public C {
                       public:
                           void doSomething() {
                                f(); // <--- This line
                           }
                       };

示された行で、への呼び出しf()は次のように解決されます。まず、考えられる名前のセットに B::fandを追加します。基本クラスがないため、何も隠しません。ただし、hideは行うので、から見えなくても到達できますが、非表示と見なされ、名前を付けることができるオブジェクトのセットから削除されます。しか残っていないので、それが呼ばれます。ISO 仕様は、(§10.2/7) に言及しています。D::fD::fDB::fD::fD::fAB::ffB::f

仮想基底クラスを使用すると、非表示の宣言を通過しないサブオブジェクト ラティスを通るパスに沿って、非表示の宣言に到達できます。これは曖昧ではありません。[...]

これは上記のルールによるものだと思います。

C++11 (ドラフト仕様 N3242 による) では、ルールは以前よりもはるかに明示的に記述され、名前の意味を計算するための実際のアルゴリズムが与えられます。これが言語のステップバイステップです。

§10.2/3 から始めます。

S(f, C) と呼ばれる C の f のルックアップ セットは、2 つのコンポーネント セット構成されます。サブオブジェクト set、これらのメンバーの宣言 (おそらく using 宣言を含む) が見つかったサブオブジェクトのセット。宣言セットでは、using 宣言はそれらが指定するメンバーに置き換えられ、型宣言 (注入されたクラス名を含む) はそれらが指定する型に置き換えられます。S(f, C) は次のように計算されます。

このコンテキストでCは、ルックアップが発生するスコープを参照します。つまり、setは「クラス スコープでS(f, C)ルックアップしようとしたときに表示される宣言は何か? 」という意味です。これに答えるために、仕様はこれを決定するアルゴリズムを定義しています。最初のステップは次のとおりです: (§10.2/4)fC

C に名前 f の宣言が含まれている場合、宣言セットには、ルックアップが発生する言語構造の要件を満たす、C で宣言された f のすべての宣言が含まれます。[...] 結果の宣言セットが空でない場合、サブオブジェクト セットには C 自体が含まれ、計算は完了します。

つまり、クラス自体にf宣言されたものがある場合、宣言セットはf、そのクラスで定義された (または宣言でインポートされたusing) 名前の付いたもののセットです。ただし、 という名前のものが見つからないf場合、または名前が付けられたすべてfの種類が間違っている場合 (たとえば、型が必要な場合の関数宣言)、次のステップに進みます: (§10.2/5)

それ以外の場合 (つまり、C に f の宣言が含まれていないか、結果の宣言セットが空である場合)、S(f, C) は最初は空です。C に基本クラスがある場合、各直接基本クラス サブオブジェクト B iで f のルックアップ セットを計算し、そのような各ルックアップ セット S(f, B i ) を S(f, C) にマージします。

言い換えれば、基本クラスを見て、それらの基本クラスで名前が参照できるものを計算してから、すべてをマージします。マージを行う実際の方法は、次の手順で指定します。これは非常にトリッキーです (3 つの部分で構成されています)。元の文言は次のとおりです: (§10.2/6)

次の手順では、ルックアップ セット S(f, B i ) を中間 S(f, C)にマージした結果を定義します。

  • S(f, B i ) の各サブオブジェクト メンバーが S(f, C) のサブオブジェクト メンバーの少なくとも 1 つの基本クラス サブオブジェクトである場合、または S(f, B i ) が空の場合、S(f 、C) は変更されず、マージが完了します。逆に、S(f, C) の各サブオブジェクト メンバーが S(f, B i )のサブオブジェクト メンバーの少なくとも 1 つの基本クラス サブオブジェクトである場合、または S(f, C) が空の場合、新しいS(f, C) は S(f, Bi ) のコピーです。

  • それ以外の場合、S(f, B i ) と S(f, C) の宣言セットが異なる場合、マージはあいまいです: 新しい S(f, C) は、無効な宣言セットと和集合を持つルックアップ セットです。サブオブジェクト セット。その後のマージでは、無効な宣言セットは他のものとは異なると見なされます。

  • それ以外の場合、新しい S(f, C) は、宣言の共有セットとサブオブジェクト セットの和集合を含むルックアップ セットです。

よし、これを1つずつバラバラにしよう。ここでの最初のルールには 2 つの部分があります。最初の部分は、宣言の空のセットを全体のセットにマージしようとしている場合、何もしないことを示しています。それは理にかなっている。また、これまでにマージされたすべての基本クラスで何かをマージしようとしている場合は、何もしないでください。これは重要です。なぜなら、何かを非表示にした場合に、それをマージして誤って再導入したくないということを意味するからです。

最初のルールの 2 番目の部分は、マージしようとしているものがこれまでにマージされたすべてのものから派生したものである場合、この時点までに計算したセットを派生型用に計算したデータに置き換えることを示しています。 . これは本質的に、接続されていないように見える多くのクラスを一緒にマージし、それらすべてを統合するクラスにマージした場合、古いデータを捨てて、すでに計算したその派生型のデータを使用することを意味します.

では、その 2 番目の規則に進みましょう。これを理解するのに時間がかかったので、間違っているかもしれませんが、2 つの異なる基底クラスでルックアップを実行して異なるものが返された場合、名前があいまいであることを報告する必要があると言っていると思います。この時点で名前を調べようとすると間違っています。

最後のルールは、これらの特殊なケースのいずれにも該当しない場合、何も問題はなく、それらを組み合わせる必要があることを示しています。

ふぅ…大変でした!上記のダイヤモンドの継承についてこれをトレースするとどうなるか見てみましょう。fで始まる名前を調べたいとしますAAは を定義していないので、 で始まるルックアップと で始まるfルックアップの値を計算します。しばらく様子を見てみましょう。の意味の値を計算すると、が定義されていることがわかり、探すのをやめます。inを検索する値は集合 ( , } です。 in が何を意味するのかを検索するには、 inを調べて定義していないことがわかります。そのため、 から値を再帰的に検索します。fBfCfBB::ffBB::fBfCCfDDD::f, }でDあり、すべてをマージすると、規則 1 の後半が適用されることがわかります (サブオブジェクト セット内のすべてのオブジェクトが のベースであることが空虚に真であるDため) 。CD::fD

最後に、 と の値をマージする必要がBありCます。D::f{ , D} と { B::f, } のマージを試みBます。これが楽しいところです。この順番でマージするとします。D::f{ , D} と空のセットをマージすると、{ D::f, D} が生成されます。B::f{ , B}をマージするとD、 は のベースであるためB、ルール 1 の後半までに、古いセットをオーバーライドして { B::f, B} になります。したがって、 のルックアップはinfのバージョンです。fB

一方、逆の順序でマージする場合は、{ , } で開始し、{ , B::f}Bでマージを試みます。しかし、は の基底なので、無視して { , } を残します。同じ結果に達しました。かっこいいでしょ?こんなにうまくいくなんてビックリ!D::fDDBB::fB

これで、古いルールは本当に (っぽい) 簡単で、新しいルールは信じられないほど複雑ですが、なんとかうまくいくようになりました。

お役に立てれば!

于 2011-08-27T00:53:28.710 に答える