Это можно проверить во время компиляции. Ключ в том, что, если у нас есть ромбовидный узор:
![diamond](https://i.stack.imgur.com/8pRgB.png)
Вы можете однозначно бросить D&
к A&
. Однако, если наследование не является виртуальным:
![not diamond](https://i.stack.imgur.com/T4T7g.png)
бросок будет неоднозначным. Итак, давайте попробуем сделать бриллиант!
template <typename Base, typename Derived>
class make_diamond {
struct D2 : virtual Base { }; // this one MUST be virtual
// otherwise we'd NEVER have a diamond
public:
struct type : Derived, D2 { };
};
В этот момент это просто еще один тип void_t
-стиля черт:
template <typename Base, typename Derived, typename = void>
struct is_virtual_base_of : std::false_type { };
template <typename Base, typename Derived>
struct is_virtual_base_of<Base, Derived, void_t<
decltype(static_cast<Base&>(
std::declval<typename make_diamond<Base, Derived>::type&>()))
>> : std::true_type { };
Если приведение однозначно, выражение в частичной специализации будет действовать, и будут предпочтительно, чтобы специализации. Если листинг неоднозначен, у нас будет сбой замены и в конечном итоге будет основной. Обратите внимание, что Base
здесь на самом деле не нужно иметь каких-либо функций virtual
членов:
struct A { };
struct B : public A { };
struct C : virtual A { };
std::cout << is_virtual_base_of<A, B>::value << std::endl; // 0
std::cout << is_virtual_base_of<A, C>::value << std::endl; // 1
И если у него есть какие-то чистые виртуальные функции-члены, мы не должны переопределить их, так как мы никогда на самом деле построения объекта ,
struct A2 { virtual void foo() = 0; };
struct B2 : public A2 { void foo() override { } };
struct C2 : virtual A2 { void foo() override { } };
std::cout << is_virtual_base_of<A2, B2>::value << std::endl; // 0
std::cout << is_virtual_base_of<A2, C2>::value << std::endl; // 1
Конечно, если ваш класс помечен final
, это не будет работать. Но тогда, если бы это было final
, не имело бы значения, какое у него было наследство.
Возможно, теперь есть лучшие способы сделать это, но в VS2010 есть ключевое слово Microsoft, называемое '__interface'. Однако это a) не переносимо и b) вы должны быть осторожны, поскольку по какой-то причине этим интерфейсам не разрешается иметь виртуальный деструктор. – Excelcius
Это больше похоже на техническую спецификацию, чем на функциональные требования. Зачем тебе это нужно? –
@Excelcius Мы разрабатываем как для Linux, так и для Windows; Я перечислил VS2010, потому что gcc впереди в поддержке C++ 11. – Angew