Как определить параметры шаблона базовых классов во время компиляции (на предмет ошибок)?

Я использовал странно повторяющийся шаблон шаблона. Общий код выглядит следующим образом:

template <typename T> void genericFunction(T &);
template <typename T> struct Functionality {
    void genericMethod() {
        genericFunction(*((T *)this)) ;
    }
};

struct Klass : public Functionality<Klass> {};

void main() {
    Klass obj ;
    obj.genericMethod();
}

template <> void genericFunction<Klass>(Klass &obj) {
    //do stuff with Klass &obj here
}

Сегодня я столкнулся с ошибкой, которая стоила мне около 90 минут нервотрепки. Эта ошибка была вызвана использованием неправильного параметра шаблона для моего объявления наследования базового класса, примерно так:

struct Klass : public Functionality<SomeOtherKlass> {}; //SomeOtherKlass wrong!!!

Я хотел бы улучшить свой код, чтобы это несоответствие между производным классом и параметром шаблона базового класса обнаруживалось (время выполнения, время компиляции, в любое время :)), возможно ли это? Спасибо.


person Gearoid Murphy    schedule 08.01.2012    source источник


Ответы (5)


Вы можете утверждать отношение, например. genericMethod() с использованием функций Boost или C++11:

BOOST_STATIC_ASSERT(( boost::is_base_of<Functionality<T>, T>::value ));

... хотя это предполагает, что другой класс также не является производным от Functionality<T>.

Альтернативой может быть утверждение отношения во время выполнения в тестовых сборках:

template <typename T> struct Functionality {
#ifdef TEST_BUILD
    virtual ~Functionality() {}
#endif
    void genericMethod() {
#ifdef TEST_BUILD
        assert(dynamic_cast<T*>(this));
#endif
        genericFunction(*((T *)this)) ;
    }
};

Обратите внимание, что тест не будет работать внутри конструкторов и деструкторов.

person Georg Fritzsche    schedule 08.01.2012
comment
К сожалению, это может быть, в зависимости от контекста - person Gearoid Murphy; 08.01.2012
comment
Я думаю, что это будет полной защитой, если мы сможем организовать Функциональность‹T›, чтобы гарантировать, что она создается только один раз для каждого T? Имеет ли это смысл? Является ли это возможным? - person Aaron McDaid; 09.01.2012
comment
@GeorgFritzsche, теперь я вижу это и в конце концов изменил свой комментарий. Если для каждой Функциональности ‹T› мы можем показать, что она является основой T, то я думаю, что мы проделали большую часть пути. Самое интересное (и сложное?) было в том, чтобы затем попытаться показать, что Функциональность‹T› создается не более одного раза для каждого T. - person Aaron McDaid; 09.01.2012
comment
@Aaron: Вы бы не хотели предотвратить несколько экземпляров одного и того же производного класса. - person Georg Fritzsche; 09.01.2012
comment
Возможно, «несколько экземпляров» — неправильная формулировка. Представьте себе struct Y : Functionality<X> {}; struct Z : Functionality<X> {}; Этот код неверен на многих уровнях!, но, в частности, было бы здорово обнаружить, что есть два класса, которые наследуются от Functionality‹X›. Это проблема «двойного экземпляра». - person Aaron McDaid; 09.01.2012
comment
@Aaron: Что касается второго комментария: если struct B : A<B> {}; и struct C : A<B> {};, то сохраняются и is_base<A<B>,B>, и is_base<A<B>,C>. Тем не менее, есть только один класс A<B> и нет возможности найти его подклассы. - person Georg Fritzsche; 09.01.2012

В С++ 11 должно работать следующее:

template<typename T> class Base
{
  friend T; // allowed in C++11
private:
  ~Base() {}
public:
  // ...
};

class Derived: public Base<Derived> {}; // OK

class WronglyDerived: public Base<Derived> {}; // Error: destructor of base class is private
person celtschk    schedule 08.01.2012
comment
Учитывая поддержку C++11, это намного лучше. - person Georg Fritzsche; 09.01.2012
comment
Есть версия, которая работает до C++11, но, по-видимому, это расширение g++. Поместите этот подкласс в Функциональность struct sub { typedef T T_; }; friend class sub :: T_; - person Aaron McDaid; 09.01.2012

Вы можете использовать dynamic_cast, который вернет null, если у вас неправильный тип параметра. (Для этого вам понадобится хотя бы одна виртуальная функция в базе — скажем, деструктор.)

Если вы беспокоитесь об эффективности, у boost есть polymorphic_cast, который выполняет динамическое приведение в режиме отладки, но статическое приведение для производства.

(И в любом случае было бы неплохо избежать использования приведения в стиле C.)

person Alan Stokes    schedule 08.01.2012
comment
Я начал использовать этот шаблон шаблона, чтобы избежать снижения производительности, вызванного виртуальными функциями, genericFunction в приведенном выше примере автоматически генерируется сценарием, который создает функции сериализации для связанного класса (чтение и запись в std::iostream) - person Gearoid Murphy; 08.01.2012
comment
Будет ли проблемой добавление виртуальной функции, которая никогда не вызывается или вызывается только один раз? Вам не нужно делать все функции виртуальными. - person Alan Stokes; 08.01.2012
comment
Можно было бы так подумать, но я заметил падение производительности для функций, которые не были виртуализированы, простого наследования от базового класса с чисто виртуальными функциями было достаточно, чтобы вызвать снижение производительности, мой конкретный контекст использует огромное количество древовидных структур, поэтому любое незначительное влияние увеличено, к сожалению - person Gearoid Murphy; 08.01.2012
comment
@Gearoid: Как насчет того, чтобы чек и т. Д. Компилировались только в тестовых сборках? - person Georg Fritzsche; 08.01.2012
comment
@Georg, такое решение было бы хорошо, но динамическое приведение, похоже, не выявляет раскол, как я могу использовать RTTI из контекста класса Функциональность, чтобы выполнить эту проверку? - person Gearoid Murphy; 08.01.2012
comment
@Gearoid А? dynamic_cast — это RTTI. - person Alan Stokes; 08.01.2012
comment
@Gearoid: если установлен какой-либо макрос тестовой сборки, добавьте некоторую виртуальную функцию и попробуйте dynamic_cast<T*>(this). - person Georg Fritzsche; 09.01.2012
comment
@Alan, я знаю, что RTTI поддерживает функцию dynamic_cast, я реализовал предложение Георга только для отладочных сборок, но безуспешно. Функция динамического приведения не возвращает значение, даже если параметры шаблона верны, я обновлю ответ, иллюстрирующий мой подход... - person Gearoid Murphy; 09.01.2012

Предположим, вы добавляете в базу шаблонный конструктор, который принимает указатель на произвольный тип;

template<class U> Functionality(U *) { ... }

Затем конструктор каждого производного класса может передать свой указатель this конструктору, а в теле конструктора вы просто статически утверждаете, что U и T являются одним и тем же типом.

Параметр конструктора фактически никогда не используется, поэтому его следует полностью оптимизировать. И если это единственный конструктор базового класса, вы не можете забыть его вызвать. Единственная проблема была бы, если бы вы передали что-то другое, кроме этого.

person Alan Stokes    schedule 08.01.2012
comment
Можно ли реализовать это без ручного кодирования для каждого производного класса? - person Gearoid Murphy; 08.01.2012
comment
@Gearoid Нет. Разве вы не можете просто сгенерировать его автоматически? - person Alan Stokes; 08.01.2012
comment
К сожалению, сами производные классы не генерируются автоматически, автоматически создаются только функции загрузки/сохранения для производных классов. - person Gearoid Murphy; 09.01.2012

На данный момент наиболее реальным предложением является использование dynamic_cast для выявления искаженных объявлений наследования в конструкторе базового класса, например:

#include <iostream>
template <typename T> struct Base {
    Base() {
        std::cout<<dynamic_cast<T *> (this)<<std::endl;
    }
    virtual void iampolymorphic(){}
};
struct Decoy {} ;
struct Pass : public Base<Pass>{}; //correct
struct Fail : public Base<Decoy>{}; //incorrect
int main() {
    Pass p ;
    Fail f ;
    return 1 ;
}

Этот код компилируется на g++ 4.6.1, Amd64 Xubuntu 11.10. Результатом обеих операций динамического приведения является нулевой указатель. Комментарии, критика и наблюдения приветствуются.

person Gearoid Murphy    schedule 08.01.2012
comment
Поместите тест в метод, а не в конструктор. Во время строительства это не сработает. - person Georg Fritzsche; 09.01.2012
comment
Блестяще :) работает, большое спасибо. Если бы вы могли конкретизировать ответ на этот счет и включить примечание, объясняющее, почему он не работает в конструкторе, я приму его как ответ. - person Gearoid Murphy; 09.01.2012
comment
@GearoidMurphy, по вашему собственному признанию, это не работает! (Выводом обеих операций динамического приведения является нулевой указатель). Однако, согласно комментарию Георга Фрицше, вы очень близки. - person Aaron McDaid; 09.01.2012
comment
К сожалению, это не работает и в деструкторах. Я пробовал с typeid(*this).name(). - person Aaron McDaid; 09.01.2012
comment
@Gearoid: я кое-что сжал в своем ответе. - person Georg Fritzsche; 09.01.2012