Что такое множественное повторное наследование?

Я называю следующее "множественное повторное наследование":

  • наследование класса один раз прямо и один или несколько раз косвенно путем наследования одного или нескольких его потомков
  • наследуя класс косвенно два или более раза, наследуя двух или более своих потомков

Я хочу знать, существует ли он и как однозначно обращаться к встроенным подобъектам.

1.) [Professional С++, 2 nd ed.] указывает, что компилируемая программа не может иметь класс, который непосредственно наследует как своего непосредственного родителя, так и указанного родителя родительский класс. Это правда?

Учитывая GrandParent и Parent, который расширяет GrandParent, VC12 и g++ позволяют GrandChild напрямую наследовать как от Parent, так и от GrandParent. В VC12 и g++ его можно определить следующим образом:

GrandParent объявляет элемент данных int num. Parent объявляет свой собственный num в дополнение к наследованию GrandParent num. GrandChild объявляет свой собственный num в дополнение к наследованию Parent и GrandParent 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.) Почему (a) gc.Parent::GrandParent::num неоднозначно в g++, когда (b) gc.Parent::num нет? (а) однозначно описывает свое местоположение в дереве наследования. gc имеет только один подобъект 1 Parent, у которого есть только один GrandParent подобъект, который имеет только 1 num. Для (b) gc имеет один Parent, который имеет свой собственный num, но также подобъект GrandParent с другим num.

3.) Для gc.GrandParent::num кажется, что VC12 смотрит в базовый подобъект gc непосредственного GrandParent для последнего num. Я предполагаю, что он однозначен в том, что его поиск по имени определяется gc, поэтому сущность справа от . просматривается сначала в области gc, а самая непосредственная GrandParent - gc scope является непосредственно унаследованным, а не косвенно унаследованным через Parent. Я не прав?

4.) Почему gc.GrandParent::num неоднозначно относится к g++, когда gc.Parent::num нет? Если человек неоднозначен, то не должны ли быть одинаково двусмысленными? Для предыдущего gc имеет два GrandParent s; и для последнего Parent имеет 2 num s.


Грегуар, Марк Р. и др. Professional С++, 2 nd ed. Индианаполис, IN: Wiley Pubishing, 2011. p. 241. Печать.

Ответ 1

Общим термином для этого является шаблон алмаза (или проблема с алмазами).

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

Одним из способов было бы сделать базу косвенной. Новые функции наследующих конструкторов в С++ 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:: для устранения неоднозначности доступа членов.

Ответ 2

Я добавляю к принятому ответу. Он утверждает, что производный класс не может получить доступ к прямому классу base, если производный класс также косвенно наследует base. Его решение делает космос base косвенным, обертывая его шаблоном, аргументом второго типа которого является tag. Это гарантирует, что base является косвенным для производного класса, если производный класс расширяет обернутую базу уникальным tag. В приведенном ниже примере будет использоваться не-тип tag.

Если алмазоподобная задача была обобщена, чтобы содержать больше поколений в виде:

  • я th наследует от base и (i-1) th,
  • (i - 1) th наследует от base и (i - 2) th,
  • ... и
  • 2 nd наследует от base,

то это импровизированный контейнер, в котором каждый элемент хранится в каждом уникальном теге base. В этом случае tag -производство должно быть автоматизировано. Один из способов - объединить все производные классы с помощью шаблона, отличного от типа. Его параметр non-type 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;
};

//improvised_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) th версию самого себя в подобъекте (i-1) th декрементируя N пока он не станет равным index – 1, при этом каждый вызов переместит одну область глубже и, наконец, вернет элемент целевой области. Он проверяет index – 1, а не index, так как специализация ctor <improvised_container 0 thimprovised_container delete d. at компенсирует "выключение".

improvised_container использует protected наследование, чтобы клиентский код не обращался к методам at и size своих базовых подобъектов. Размер подобъектов меньше, чем окружающие объекты.

Это работает в g ​​++ 4.8. Наследующий конструктор using base::base вызывает ошибки в VC12, но его можно опустить, поскольку тип элемента int.