Как реализовать проверку во время компиляции, что downcast действителен в CRTP? - PullRequest
8 голосов
/ 06 мая 2011

У меня обычный старый CRPT (пожалуйста, не отвлекайтесь на ограничения доступа - вопрос не о них):

 template<class Derived>
 class Base {
     void MethodToOverride()
     {
        // generic stuff here
     }
     void ProblematicMethod()
     {
         static_cast<Derived*>(this)->MethodToOverride();
     } 
 };

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

 class ConcreteDerived : public Base<ConcreteDerived> {
     void MethodToOverride()
     {
        //custom stuff here, then maybe
        Base::MethodToOverride();
     }
 };

Теперь, когда static_cast беспокоит меня. Мне нужен downcast (не upcast), поэтому я должен использовать явное приведение. Во всех разумных случаях приведение будет действительным, поскольку текущий объект действительно является производным классом.

Но что, если я каким-то образом изменю иерархию, и приведение теперь станет недействительным?

Могу ли я каким-то образом принудительно проверить проверку во время компиляции, что в этом случае допустим явный downcast?

Ответы [ 5 ]

5 голосов
/ 06 мая 2011

Во время компиляции вы можете проверять только статические типы, и это то, что static_cast уже делает.

Учитывая Base*, оно известно только во время выполнения и может быть известно только во время выполнения.каков его тип dynamic , то есть указывает ли он на ConcreteDerived или что-то еще.Поэтому, если вы хотите проверить это, это должно быть сделано во время выполнения (например, с помощью dynamic_cast)

4 голосов
/ 06 мая 2011

Для дополнительной безопасности вы можете добавить защищенный конструктор в Base, чтобы убедиться, что что-то получено из него. Тогда единственной проблемой будет для действительно глупого:

class ConcreteDerived : public Base<SomeOtherClass>

но это должно быть уловлено первым обзором кода или тестовым примером.

3 голосов
/ 06 мая 2011

Чтобы расширить то, что сказал @Bo Persson, вы можете выполнить проверку времени компиляции в этом конструкторе, используя, например, Boost.TypeTraits или C ++ 0x / 11 <type_traits>:

#include <type_traits>

template<class Derived>
struct Base{
  typedef Base<Derived> MyType;

  Base(){
    typedef char ERROR_You_screwed_up[ std::is_base_of<MyType,Derived>::value ? 1 : -1 ];
  }
};

class ConcreteDerived : public Base<int>{
};

int main(){
  ConcreteDerived cd;
}

Полный пример на Ideone .

2 голосов
/ 28 июня 2012

Кажется, что существует способ проверить правильность CRPT во время компиляции.

Делая Base абстрактной (добавляя в Base некоторый чистый виртуальный метод), мы гарантируем, что любой экземпляр Base является частью некоторого производного экземпляра.,

Делая все конструкторы Base частными, мы можем предотвратить нежелательное наследование от Base.

Объявляя Derived как друга Base, мы разрешаем единственное наследование, ожидаемое CRPT.

После этого, Преобразование CRPT должно быть правильным (поскольку что-то наследуется от базы, и это «что-то» может быть только производным, а не каким-то другим классом)

Возможно, для практического использования первый шаг (создание абстрактного объекта Base) является избыточным, поскольку успешныйstatic_cast гарантирует, что Derived находится где-то в базовой иерархии.Это допускает только экзотическую ошибку, если Derived наследуется от Base <Derived> (как ожидает CRPT), но в то же время Derived создает другой экземпляр Base <derived> (без наследования) где-то в производном коде (может, потому что он друг),Однако я сомневаюсь, что кто-то может случайно написать такой экзотический код.

1 голос
/ 06 мая 2011

Когда вы делаете что-то вроде ниже:

struct ConcreteDerived : public Base<Other>  // Other was not inteded

Вы можете создавать объекты class (производные или базовые). Но если вы попытаетесь вызвать функцию, она выдаст ошибку компиляции, относящуюся только к static_cast. ИМХО это удовлетворит все практические сценарии.

Если я правильно понял вопрос, то я чувствую, что ответ находится в самом вашем вопросе. :)

...