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

13 Фев
2012

Опишу одну из непопулярных сторон 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;

}



То неоднозначность исчезнет, компилятор код скомпилирует, а в копилке будет еще одно универсальное решение.
По материалам Хабрахабр.



загрузка...

Комментарии:

Наверх