2013-05-09 4 views
8

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

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

+0

Возможно, теперь есть лучшие способы сделать это, но в VS2010 есть ключевое слово Microsoft, называемое '__interface'. Однако это a) не переносимо и b) вы должны быть осторожны, поскольку по какой-то причине этим интерфейсам не разрешается иметь виртуальный деструктор. – Excelcius

+1

Это больше похоже на техническую спецификацию, чем на функциональные требования. Зачем тебе это нужно? –

+0

@Excelcius Мы разрабатываем как для Linux, так и для Windows; Я перечислил VS2010, потому что gcc впереди в поддержке C++ 11. – Angew

ответ

3

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

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

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

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

1

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

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

3

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

diamond

Вы можете однозначно бросить D& к A&. Однако, если наследование не является виртуальным:

not diamond

бросок будет неоднозначным. Итак, давайте попробуем сделать бриллиант!

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

+0

Я знаю более простые подходы, чтобы проверить, является ли базовый класс виртуальным (например, кастинг, преобразования между указателями и другими и т. Д.), Но на них влияет доступность базового класса. На ваш подход, использующий проверку неоднозначности, не влияет защита доступа, поэтому +1.Однако на него влияют уже существующие двусмысленности, например. 'struct D: B, C {};' So 'is_virtual_base_of' что-то вроде' is_nonfinal_unambiguous_virtual_base_of';) – dyp

+0

@dyp Не знаете, что вы подразумеваете под существующими двусмысленностями? – Barry

+0

'D d; A & a = d; 'неоднозначно: http://coliru.stacked-crooked.com/a/d92de17312866f0b – dyp

 Смежные вопросы

  • Нет связанных вопросов^_^