Доминирование в виртуальном наследовании - PullRequest
13 голосов
/ 27 августа 2011

Каковы точные правила стандартов C ++ 98 / C ++ 03 и будущего стандарта C ++ 0x для доминирования в виртуальном наследовании ?

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

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

1 Ответ

25 голосов
/ 27 августа 2011

Я думаю, что это тот язык, который вы ищете.В спецификации ISO C ++ 03, в §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 (§10.2 / 7) указано, что

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

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

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

Мы начнем с §10.2 / 3:

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

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

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

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

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

Другими словами, мы собираемся взглянуть на базовые классы, вычислить, на что может ссылаться имя в этих базовых классах, а затем объединить все вместе. Фактический способ слияния указан на следующем шаге. Это действительно сложно (у него есть три части), так что здесь за ударом. Вот оригинальная формулировка: (& 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 и видим, что это не такопределить 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 является версиейn из f в B.

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...