Я называю следующее «множественное повторное наследование»:
Я хочу знать, существует ли он и как однозначно получить доступ к встроенным подобъектам.
1.) [Профессиональный C ++, 2й ред.]† утверждает, что скомпилируемая программа не может иметь класс, который напрямую наследует как ее непосредственного родителя, так и родительский класс указанного родителя. Это правда?
Учитывая GrandParent
а также Parent
, который расширяет GrandParent
, VC12 и G ++ позволяет GrandChild
напрямую наследовать от обоих Parent
а также GrandParent
, В VC12 и g ++ эти классы можно определить следующим образом:
GrandParent
объявляет int num
элемент данных. Parent
объявляет свое num
в дополнение к наследованию GrandParent
«s num
, GrandChild
объявляет свое num
в дополнение к наследованию Parent
и GrandParent
«s num
s.
VC12, по-видимому, допускает однозначный доступ к элементам по всем направлениям, но g ++ допускает его только в некоторых случаях.
#include <iostream>
using std::cout;
using std::endl;
struct GrandParent { int num; };
struct Parent : GrandParent { int num; };
struct GrandChild : GrandParent, Parent { int num; };
int main()
{
GrandChild gc;
gc.num = 2;
gc.Parent::num = 1;
gc.Parent::GrandParent::num = 0; // g++ error: ‘GrandParent’ is an ambiguous base of ‘GrandChild’
gc.GrandParent::num = 5; // g++ error: ‘GrandParent’ is an ambiguous base of ‘GrandChild’
// --VC12 output; g++ output--
cout << gc.num << endl; // 2 ; 2
cout << gc.Parent::num << endl; // 1 ; 1
cout << gc.Parent::GrandParent::num << endl; // 0 ; N/A due to above error
cout << gc.GrandParent::num << endl; // 5 ; N/A due to above error
}
2.) Почему (а) gc.Parent::GrandParent::num
неоднозначно в g ++, когда (б) gc.Parent::num
нет? (а) однозначно описывает его местоположение в дереве наследования. gc
только 1 Parent
подобъект, который имеет только 1 GrandParent
подобъект, который имеет только 1 num
, Для (б), gc
имеет один Parent
, который имеет свой num
но также GrandParent
подобъект с другим num
,
3.) Для gc.GrandParent::num
Вроде VC12 смотрит в gc
Сразу GrandParent
базовый подобъект для последнего num
, Я предполагаю, причина этого однозначна в том, что это поиск имени, gc
Таким образом, сущность справа от .
ищется первым в gc
и самые непосредственные GrandParent
в gc
сфера действия напрямую наследуется, а не косвенно наследуется через Parent
, Я ошибся?
4.) Почему gc.GrandParent::num
неоднозначно для g ++, когда gc.Parent::num
нет? Если человек неоднозначен, то не должны ли оба быть одинаково неоднозначными? Для предварительного, gc
имеет два GrandParent
s; и для последнего, Parent
имеет 2 num
s.
†Gregoire, Marc R. et al. Профессиональный C ++, 2й редактор Индианаполис, IN: Wiley Publishing, 2011. с. 241. Печать.
Общий термин для этого ромбовидный узор (или же алмазная проблема).
Это не ошибка как таковая, но, как отмечено в комментариях, любая попытка получить доступ к прямой базе, которая дублируется в другом месте иерархии, приведет к ошибке неоднозначности.
Один из обходных путей — сделать базу непрямой. Новая особенность конструкторов наследования в C ++ 11 позволяет создавать идеальные оболочки:
template< typename base, typename tag >
struct disambiguated_base : base
{ using base::base; };
Учитывая неиспользуемый тип тега, он генерирует новый класс, производный и функционально идентичный данной базе. Тип тега может быть неполным классом, обозначаемым подробным спецификатором типа:
struct GrandChild : Parent,
disambiguated_base< GrandParent, class grandchild_grandparent_tag > {
typedef disambiguated_base< GrandParent, grandchild_grandparent_tag >
my_direct_grandparent;
int num;
};
Сейчас GrandChild
можешь использовать my_direct_grandparent::
для устранения неоднозначности доступа членов.
Я добавляю к принятому ответу. В нем говорится, что производный класс не может получить доступ к прямому base
класс, если производный класс также косвенно наследует base
, Его решение делает base
косвенный класс, обернув его шаблоном, аргумент второго типа которого tag
, Это обеспечивает base
является косвенным по отношению к производному классу при условии, что производный класс расширяет упакованную базу уникальным tag
, В приведенном ниже примере будет использоваться не тип tag
,
Если бы подобная алмазу проблема была обобщена, чтобы содержать больше поколений в форме:
base
и (я — 1)го,base
и (я — 2)го,base
,тогда это импровизированный контейнер, где каждый элемент хранится в каждом уникально помеченном base
, В таком случае, tag
Изготовление должно быть автоматизировано. Одним из способов является объединение всех производных классов с помощью нетипового шаблона. Свой нетипичный параметр N
Можно указать количество рекурсивных итераций наследования. Делая tag
это не типовой параметр, значение параметра, определяющего количество подклассов, может быть однозначно связано со значением одного тега для каждого типа подобъекта. Например, tag = 10
соответствует N = 10
, который относится к 10-му поколению по иерархии:
// disambiguated_wrapper.h
struct int_wrapper {
int num;
};
template < typename base, unsigned int tag >
struct disambiguated_wrapper : base {
using base::base;
};
//impvised_container.h
#include "disambiguated_wrapper.h"
template <unsigned int N>
struct improvised_container :
protected disambiguated_wrapper<int_wrapper, N>,
protected improvised_container<N - 1> {
unsigned int size() const { return N; }
int& at(const unsigned int index) {
if (index >= N) throw "out of range";
else return (index == N - 1) ?
this->disambiguated_wrapper<int_wrapper, N>::num :
this->helper(index);
}
protected:
int& helper(const unsigned int index) {
return (index == N - 1) ?
this->disambiguated_wrapper<int_wrapper, N>::num :
this->improvised_container<N - 1>::helper(index);
}
};
#include "specializations.h"
// specializations.h
template <>
struct improvised_container<0> {
improvised_container() = delete;
}; // ^ prohibits 0-length container
template <>
struct improvised_container<1> :
protected disambiguated_wrapper<int_wrapper, 1> {
unsigned int size() const { return 1; }
int& at(const unsigned int index) {
if (index != 0) throw "out of range";
else return this->disambiguated_wrapper<int_wrapper, 1>::num;
}
protected:
int& helper(const unsigned int index) {
if (index != 0) throw "out of range";
else return this->disambiguated_wrapper<int_wrapper, 1>::num;
}
};
// main.cpp
#include "improvised_container.h"#include <iostream>
int main() {
improvised_container<10> my_container;
for (unsigned int i = 0; i < my_container.size(); ++i) {
my_container.at(i) = i;
std::cout << my_container.at(i) << ",";
} // ^ Output: "0,1,2,3,4,5,6,7,8,9,"}
Элемент доступа at
не может уменьшить index
рекурсивно называть себя, потому что index
не является константой времени компиляции. Но N
является. Так, at
звонки helper
, который рекурсивно вызывает (i — 1)го версия себя в (я — 1)го подобъект, убывающий N
пока это не будет равно index – 1
при каждом вызове перемещение на одну область глубже и, наконец, возвращение элемента целевой области. Это проверяет против index – 1
и не index
потому что 0го improvised_container
Ctor специализации delete
д. at
компенсирует офф-по-одному.
improvised_container
использования protected
наследование, чтобы предотвратить доступ клиентского кода к at
а также size
методы его базовых подобъектов. Размер подобъекта меньше размера вложенного объекта.
Это работает в g ++ 4.8. Наследующий конструктор using base::base
вызывает ошибки в VC12, но это может быть опущено, потому что тип элемента int
,