Сила, исходящая из класса виртуально

У нас есть специальная структура для интерфейсов в нашем проекте, и часть требований состоит в том, что классы, которые представляют интерфейс, могут использоваться только как виртуальные базовые классы, а не как не виртуальные. Есть ли способ обеспечить это в коде? То есть выдает ошибку компиляции, если класс является производным не виртуально.

У меня есть доступ к C ++ 11, как это реализовано в VS 2010: это означает static_assert, enable_if а также <type_traits> доступны.

8

Решение

ИМО, нет чистого и независимого от платформы решения этой проблемы.

Лучший способ — вручную перейти и изменить каждое наследство на virtual наследование.
Для этого нужно определить производные классы вашего интерфейса (скажем, class Base) это просто(!). Для этого можно выполнить следующие шаги:

  1. Делать class Base как final (C ++ 11); то есть class Base final { ...
  2. Скомпилируйте код, он сгенерирует ошибку компилятора для всех своих
    производные классы
  3. Идите и проверьте каждый производный класс и сделайте наследование как
    virtual
  4. Удалить final Ключевое слово и успешно скомпилировать код

Этот процесс (к сожалению) должен выполняться периодически, когда вы хотите провести такую ​​проверку работоспособности.

3

Другие решения

Это можно проверить во время компиляции. Ключ в том, что, если у нас есть ромбовидный узор:

бриллиант

Вы можете однозначно разыграть 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в любом случае, не имеет значения, какое у него было наследство.

3

Интересная проблема. Вы можете быть в состоянии приблизиться к тому, что вы хотите, скрыв класс интерфейса и выставив конкретный класс, который наследуется от интерфейса виртуально. Это, очевидно, влечет за собой некоторые обходные пути и неловкость, но это может быть адаптировано к вашим потребностям. Вот пример:

#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() это может быть использовано для наследования, но те, которые я попробовал, привели к ошибкам компилятора, которые деструктор был защищен, поэтому я ожидаю, что, если это возможно, это по крайней мере относительно сложно и может быть достаточно для ваших нужд.

1
По вопросам рекламы [email protected]