Множественное наследование и доступ к неоднозначным элементам - PullRequest
0 голосов
/ 28 октября 2019

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

Сначала приведу очень простой пример того, о чем я говорю:

#include <memory>

struct A { };
struct B : public A { };
struct C : public B, public A { };

int main() {
    std::shared_ptr<A> p0 = std::make_shared<B>(); // possible
    std::shared_ptr<A> p1 = std::make_shared<C>(); // impossible

    // Here the compiler obviously whines "‘A’ is an ambiguous base of ‘C’"
    // but if we're especially naughty, we can circumvent it with a void* cast
    // we still can't really use this pointer to anything though.
    A* p2 = static_cast<A*>((void*)new C()); 
}

Как свидетельствуют комментарии, я могу быть особенно непослушным и полностью игнорировать тип. Эта порочность приведет меня прямо в ад разработчика и не позволит мне использовать указатель так, как мне бы того хотелось, так что на самом деле у меня все в порядке с базой std::shared_ptr<B>. Но второй (и немного ближе к реальности) пример показывает, что этого компромисса недостаточно:

#include <iostream>
#include <memory>

int current_val = 0;

struct A {
    int runtime_val;
    A(int v) : runtime_val(v) 
    { 
        std::cout << "ACtor with arg: " << v << std::endl; 
    }
};

template<typename T>
struct B : public A {
    static const int global_val;
    B() : A(global_val) {}
};

template<typename T>
const int B<T>::global_val{current_val++};

struct X : public B<X> {};           // Base 
struct C : public X, public B<C> {}; // Leaf
struct D : public X, public B<D> {}; // Leaf

int main() {
    std::cout << "Xval: " << B<X>::global_val << std::endl; // 0
    std::cout << "Dval: " << B<D>::global_val << std::endl; // 1
    std::cout << "Cval: " << B<C>::global_val << std::endl; // 2

    // std::shared_ptr<A> px = std::make_shared<X>(); // Impossible because of ambiguity
    std::shared_ptr<X> p0 = std::make_shared<X>(); // I'm fine with this, really
    std::shared_ptr<X> p1 = std::make_shared<D>();
    std::shared_ptr<X> p2 = std::make_shared<C>();

    std::cout << "p0 val: " << p0->runtime_val << " (X)" << std::endl; // 0     :)
    std::cout << "p1 val: " << p0->runtime_val << " (D)" << std::endl; // 0     :(
    std::cout << "p2 val: " << p0->runtime_val << " (C)" << std::endl; // 0    >:(
}

В этом примере я использую B<T>::global_val в качестве своего рода информации о типе среды выполнения для данного типа <T>. Когда программа запускается, я получаю следующий вывод (для ясности я добавил несколько дополнительных комментариев):

Xval: 0
Dval: 1
Cval: 2
ACtor with arg: 0  -- (p0 initialization)
ACtor with arg: 0  -- (p1 initialization (X base))
ACtor with arg: 1  -- (p1 initialization (D base))
ACtor with arg: 0  -- (p2 initialization (X base))
ACtor with arg: 2  -- (p2 initialization (C base))
p0 val: 0 (X)
p1 val: 0 (D)
p2 val: 0 (C)

Кажется, что v-таблица хочет указывать только на X -базу моегоD и C классы. Как я могу гарантировать, что D и C экземпляры runtime_val будут указывать на листья дерева наследования, а не на базу?

PS Я пытался создать базовый класс Xчисто виртуальный, но не повезло.

Ответы [ 3 ]

2 голосов
/ 28 октября 2019
std::shared_ptr<A> p1 = std::make_shared<C>(); // impossible

Чтобы прояснить, почему это невозможно, компилятор не сможет узнать, на какую из A баз вы указали указывать.

Если выОн указывает на базу A базы B, что возможно при явном преобразовании:

std::shared_ptr<A> p1 = std::static_pointer_cast<B>(std::make_shared<C>());

Но на самом деле нет никакого способа обратиться к прямой базе A, потому чтодвусмысленности. Вы можете добавить дополнительную обертку к другой базе:

struct A          {};
struct B  : A     {};
struct B2 : A     {};
struct C  : B, B2 {};

std::shared_ptr<A> p1 = std::static_pointer_cast<B >(std::make_shared<C>());
std::shared_ptr<A> p2 = std::static_pointer_cast<B2>(std::make_shared<C>());
0 голосов
/ 04 ноября 2019

В комментариях @ прежняя известная-463035818 дала ответ: по сути, это «проблема бриллианта», разрешимая с помощью виртуального наследования:

/* ... */
template<typename T>
struct B : public virtual A { // <---- Virtual inheritance
    static const int global_val;
    B() : A(global_val) {}
};
/* ... */

struct X : public B<X> { 
    X() : A(B<X>::global_val) {} // Implementation classes should then initialize their A's individually
};
struct C : public X, public B<C> {
    C() : A(B<C>::global_val) {} // Implementation classes should then initialize their A's individually
};
struct D : public X, public B<D> { 
    D() : A(B<D>::global_val) {} // Implementation classes should then initialize their A's individually
};
0 голосов
/ 28 октября 2019

Это выглядит неправильно:

struct B : public A { };
struct C : public B, public A { };

C - это B, который, в свою очередь, является A. Так что, спрашивая C, чтобы также быть A, чувствует себя неправильно.

Мой компилятор предупреждает:

1> D: \ projects \ scratch \ scratch.cpp (6,19): предупреждение C4584: 'C': базовый класс 'A' уже является базовым-класс 'B' 1> D: \ projects \ scratch \ scratch.cpp (4): сообщение: см. объявление 'A' 1> D: \ projects \ scratch \ scratch.cpp (5): сообщение: см. объявлениеиз 'B'

И удаление дублирующего наследования исправляет первый фрагмент:

struct B : public A { };
struct C : public B/*, public A*/ { };

Отсюда, реальный вопрос: вам нужно несколько экземпляров A в качестве базы?

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