Подтвердить что ты не робот

Доминирование в виртуальном наследовании

Что такое стандарты С++ 98/С++ 03 и стандартные стандартные правила С++ 0x для доминирования в виртуальном наследовании?

Я не прошу только конкретных абзацев, хотя я также прошу об этом (где-то в разделе 10, я бы предположил).

Я также спрашиваю о последствиях стандартного, пояснил стандарт.

4b9b3361

Ответ 1

Я думаю, что это тот язык, который вы ищете. В спецификации С++ 03 ISO в разделе & section 10.2/2 мы имеем следующее:

Следующие шаги определяют результат поиска по имени в области класса, C. Сначала каждое объявление для имя в классе и в каждом из под-объектов базового класса. Имя члена f в одном под-объекте B скрывает имя элемента f в под-объекте A, если A является под-объектом базового класса B. Любые объявления которые настолько скрыты, устраняются из рассмотрения. Каждая из этих деклараций, введенная Использование-декларация считается от каждого под-объекта C, который относится к типу, содержащему декларацию, указанную с помощью объявления-объявления. Если результирующий набор объявлений не все из под-объектов того же типа, или набор имеет нестатический член и включает в себя элементы из отдельных под-объектов, существует двусмысленность и программа плохо сформированы. В противном случае этот набор является результатом поиска.

На высоком уровне это означает, что при попытке поиска имени он просматривает все базовые классы и сам класс, чтобы найти объявления для этого имени. Затем вы проходите класс за классом, и если один из этих базовых объектов имеет что-то с этим именем, он скрывает все имена, введенные в любом из этих базовых классов объектов.

Важная деталь здесь:

Любые объявления которые так скрыты, устранены из рассмотрения.

Важно отметить, что если что-то скрыто от чего-либо, оно считается скрытым и удаленным. Так, например, если я это сделаю:

                            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::f и D::f к набору имен, которые можно было бы рассмотреть. D::f ничего не скрывает, потому что D не имеет базовых классов. Однако B::f скрывает D::f, поэтому даже если D::f может быть достигнут из A без просмотра B::f, он считается скрытым и удаленным из набора объектов, который может быть назван f. Поскольку остается только B::f, то тот, который вызвал. В спецификации ISO упоминается (& sect; 10.2/7), что

При использовании виртуальных базовых классов скрытая декларация может быть достигнута по пути через под-объект которая не проходит через скрытую декларацию. Это не двусмысленность. [...]

Я думаю, что это из-за вышеизложенного правила.

В С++ 11 (в соответствии с проектом спецификации N3242) правила прописаны гораздо более явно, чем раньше, и фактический алгоритм задан для вычисления имени. Ниже приведен язык, шаг за шагом.

Начнем с & sect; 10.2/3:

Набор поиска для f в C, называемый S (f, C), состоит из двух компонентных наборов: набора декларации, набора элементов имя f; и подобъектный набор, набор подобъектов, в которых были найдены объявления этих членов (возможно, включая использование-объявления). В наборе объявлений использование-объявления заменяются членами, которые они обозначать и вводить типы (включая имена вложенных классов) заменяются типами, которые они обозначают. S (f, C) рассчитывается следующим образом:

В этом контексте C относится к области, в которой происходит поиск. В других словах набор S(f, C) означает "каковы объявления, которые видны, когда я пытаюсь найти f в классе scope C?" Чтобы ответить на это, спецификация определяет алгоритм для определения этого. Первый шаг заключается в следующем: (& sect; 10.2/4)

Если C содержит объявление имени f, набор объявлений содержит каждое объявление f, указанное в C, который удовлетворяет требованиям языковой конструкции, в которой происходит поиск. [...] Если результирующий набор объявлений не пуст, то в подобъекте содержится C и вычисление завершено.

Другими словами, если сам класс имеет что-то, называемое f, объявленным в нем, то набор объявлений представляет собой всего лишь набор объектов с именем f, определенных в этом классе (или импортированных с объявлением using). Но если мы не можем найти что-либо с именем f, или если все с именем f имеет неправильный вид (например, объявление функции, когда мы хотим тип), переходим к следующему шагу: ( & раздел; 10,2/5)

В противном случае (т.е. C не содержит декларации f или результирующий набор объявлений пуст), S (f, C) изначально пуст. Если C имеет базовые классы, вычислите набор поиска для f в каждом подобъекте с прямым базовым классом B i и объедините каждый такой набор поиска S (f, B i) в свою очередь в S (f, C).

Другими словами, мы рассмотрим базовые классы, вычислим, на что имя может ссылаться в этих базовых классах, а затем объединить все вместе. Фактический способ слияния указан на следующем шаге. Это действительно сложно (у него три части), так что вот удар по дуну. Здесь оригинальная формулировка: (& sect; 10.2/6)

Следующие шаги определяют результат слияния поискового набора S (f, B i) в промежуточном S (f, C):

  • Если каждый из субобъектов S (f, B i) является подобъектом базового класса, по крайней мере, одного из подобъектов члены S (f, C), или если S (f, B i) пуст, S (f, C) не изменяется и слияние завершено. И наоборот, если каждый из субобъектов S (f, C) является подобъектом базового класса, по крайней мере, одного из субобъекты S (f, B i), или если S (f, C) пуст, новый S (f, C) является копией S (f, Bi).

  • В противном случае, если наборы объявлений из S (f, B i) и S (f, C) отличаются, слияние неоднозначно: новый S (f, C) - это набор поиска с недопустимым набором объявлений и объединением наборов подобъектов. В последующих сливается, недопустимый набор объявлений считается отличным от любого другого.

  • В противном случае новый S (f, C) является поисковым набором с общим набором объявлений и объединением субобъектные множества.

Хорошо, пусть кусок это отдельно по одному. Первое правило здесь состоит из двух частей. В первой части говорится, что если вы пытаетесь объединить пустой набор объявлений в общий набор, вы ничего не сделаете. В этом есть смысл. В нем также говорится, что если вы пытаетесь объединить что-то в этом базовом классе всего, что уже слито до сих пор, то вы вообще ничего не делаете. Это важно, потому что это означает, что если вы что-то спрятали, вы не хотите, чтобы он случайно возвращал его, объединив его обратно.

Вторая часть первого правила гласит, что если вещь, которую вы объединяете, происходит от всего, что было объединено до сих пор, вы заменяете набор, который вы вычислили до этого момента, с данными, которые вы вычислили для производный тип. Это, по сути, говорит о том, что если вы объединили много классов, которые кажутся не связанными, а затем объединяются в класс, который объединяет их все, выкидывайте старые данные и просто используйте данные для этого производного типа, которые вы уже вычислили.

Теперь откройте это второе правило. Это заняло у меня некоторое время, чтобы понять, поэтому я могу ошибаться, но я думаю, что он говорит, что если вы проводите поиск в двух разных базовых классах и возвращаете разные вещи, то это имя неоднозначно, и вы должны сообщить, что что-то есть неправильно, если вы попытаетесь найти имя на этом этапе.

Последнее правило гласит, что если мы не находимся ни в одном из этих особых случаев, ничего не получается, и вы должны просто объединить их.

Фу... это было тяжело! Посмотрим, что произойдет, когда мы проследим это за наследование алмазов выше. Мы хотим найти имя f, начиная с A. Поскольку A не определяет f, мы вычисляем значения поиска f, начиная с B и f, начиная с C. Посмотрим, что произойдет. При вычислении значения того, что f означает в B, мы видим, что B::f определено, и поэтому мы перестаем искать. Значение поиска f в B - это набор (B::f, B}. Чтобы посмотреть, что означает f в C, мы смотрим в C и видим, что он не define f, поэтому мы снова рекурсивно просматриваем значение из D. Выполнение поиска в D дает {D::f, D}, и когда мы объединяем все вместе, мы обнаруживаем, что вторая половина правила 1 применяется (поскольку оно пустое верно, что каждый объект в подобъектном множестве является базой D), поэтому окончательное значение для C определяется выражением {D::f, D}.

Наконец, нам нужно объединить значения для B и C. Это пытается слить {D::f, D} и {B::f, B}. Это - то, где это получает удовольствие. Пусть мы сходим в этом порядке. Слияние {D::f, D}, а пустое множество создает {D::f, D}. Когда мы теперь сливаемся в {B::f, B}, то, поскольку D является базой B, то во второй половине правила мы переопределяем наш старый набор и заканчиваем {B::f, B}. Следовательно, поиск f является версией f в B.

Если, с другой стороны, мы сливаемся в обратном порядке, мы начинаем с { B::f, B} и пытаемся слить в {D::f, D}. Но так как D является базой B, мы просто игнорируем его, оставляя {B::f, B}. Мы пришли к такому же результату. Довольно круто, да? Я поражен, что это так хорошо работает!

Итак, у вас есть это - старые правила действительно (иш) прямолинейны, и новые правила невероятно сложны, но каким-то образом удается все же выработать.

Надеюсь, это поможет!