Pull to refresh

Неоднозначность при множественном наследовании в C++

Опишу одну из непопулярных сторон C++. Язык поддерживает множественное наследование и, соответственно, в нем существует т.н. проблема ромба. Стандарт содержит несколько решений этой проблемы: виртуальное наследование, виртуальные деструкторы и явное указание на базовый класс (в случае с шаблонами). О последнем и пойдет речь.
Считается, что С++ не поддерживает повторное наследование т.к. нельзя явно указать на используемый базовый класс (википедия это мнение разделяет). Буквальное повторное наследование в стиле:


struct A
{
};

struct B : A, A
{
};


Конечно, недопустимо и противоречит здравому смыслу. Косвенное повторное наследование в стиле:


struct A
{
};

struct B : A
{
};

struct C : B, A
{
};


Доступно (с предупреждением компилятора), но так же лишено смысла.

Но не для случая, когда базовый класс шаблонный. Самое интересное начинается, когда базовый класс содержит шаблонную логику, например, применимую к классам-потомкам. Так, ситуация, когда надо получить свой определяемый тип «умный указатель» Ptr для каждого класса-потомка, запутает компилятор:


#include <memory>
#include <iostream>
#include <typeinfo.h>

template<typename T>
struct CSmartPointer
{
    typedef std::tr1::shared_ptr<T> Ptr;
};

struct A : CSmartPointer<A>
{
};

struct B : A, CSmartPointer<B>
{
};

struct C : B, CSmartPointer<C>
{
};

void main()
{
    std::cout << typeid(C::Ptr).name() << std::endl;
}


Компилятор увидит явную неоднозначность — какой из трёх типов A::Ptr, B::Ptr или C::Ptr выбрать, и выдаст ошибку. Эта проблема многих вводит в ступор, часто источники предлагают переименовать части базовых классов и исказить базовую архитектуру. Тем не менее, стандарт поддерживает разрешение неоднозначности — явное указание нужного базового класса через директиву using. Например:


using CSmartPointer<B>::Ptr;


Так, если явно указать, какую ветвь наследования применить внутри конкретного класса-потомка:


#include <memory>
#include <iostream>
#include <typeinfo.h>

template<typename T>
struct CSmartPointer
{
    typedef std::tr1::shared_ptr<T> Ptr;
};

struct A : CSmartPointer<A>
{
};

struct B : A, CSmartPointer<B>
{
    using CSmartPointer<B>::Ptr;
};

struct C : B, CSmartPointer<C>
{
    using CSmartPointer<C>::Ptr;
};

void main()
{
    std::cout << typeid(A::Ptr).name() << std::endl;
    std::cout << typeid(B::Ptr).name() << std::endl;
    std::cout << typeid(C::Ptr).name() << std::endl;
}


То неоднозначность исчезнет, компилятор код скомпилирует, а в копилке будет еще одно универсальное решение.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.
Change theme settings