У нас есть специальная структура для интерфейсов в нашем проекте, и часть требований состоит в том, что классы, которые представляют интерфейс, могут использоваться только как виртуальные базовые классы, а не как не виртуальные. Есть ли способ обеспечить это в коде? То есть выдает ошибку компиляции, если класс является производным не виртуально.
У меня есть доступ к C ++ 11, как это реализовано в VS 2010: это означает static_assert
, enable_if
а также <type_traits>
доступны.
ИМО, нет чистого и независимого от платформы решения этой проблемы.
Лучший способ — вручную перейти и изменить каждое наследство на virtual
наследование.
Для этого нужно определить производные классы вашего интерфейса (скажем, class Base
) это просто(!). Для этого можно выполнить следующие шаги:
class Base
как final
(C ++ 11); то есть class Base final { ...
virtual
final
Ключевое слово и успешно скомпилировать кодЭтот процесс (к сожалению) должен выполняться периодически, когда вы хотите провести такую проверку работоспособности.
Это можно проверить во время компиляции. Ключ в том, что, если у нас есть ромбовидный узор:
Вы можете однозначно разыграть D&
в A&
, Однако, если наследование не виртуальное:
актерский состав был бы неоднозначным. Итак, давайте попробуем сделать бриллиант!
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
в любом случае, не имеет значения, какое у него было наследство.
Интересная проблема. Вы можете быть в состоянии приблизиться к тому, что вы хотите, скрыв класс интерфейса и выставив конкретный класс, который наследуется от интерфейса виртуально. Это, очевидно, влечет за собой некоторые обходные пути и неловкость, но это может быть адаптировано к вашим потребностям. Вот пример:
#include <iostream>
using namespace std;
class Hide {
struct VInterface {
void foo() const { cout << "VInterface::foo()\n"; }
VInterface const &as_interface() const { return *this; }
protected:
virtual ~VInterface() { }
};
public:
struct VBase : virtual VInterface {
};
};
typedef Hide::VBase VBase;
struct VDiamond1 : VBase { };
struct VDiamond2 : VBase { };
struct VConcrete : VDiamond1, VDiamond2 { };
int main() {
VConcrete vc;
auto const &vi = vc.as_interface();
vi.foo();
}
Может быть возможно восстановить имя, используя decltype()
а также as_interface()
это может быть использовано для наследования, но те, которые я попробовал, привели к ошибкам компилятора, которые деструктор был защищен, поэтому я ожидаю, что, если это возможно, это по крайней мере относительно сложно и может быть достаточно для ваших нужд.