CRTP, чтобы избежать накладных расходов виртуальной функции-члена - PullRequest
2 голосов
/ 20 июля 2011

В CRTP, чтобы избежать динамического полиморфизма , предлагается следующее решение, чтобы избежать накладных расходов на виртуальные функции-члены и наложить определенный интерфейс:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo() {}; // required to compile. < Don't see why
};

struct your_type : base<your_type> {
  void foo() {}; // required to compile. < Don't see why
};

Однако кажется, что производный класс не требует определения для компиляции, поскольку он наследует его (код компилируется без определения my_type :: foo). Фактически, если предоставляется функция, базовая функция не будет вызываться при использовании производного класса.

Итак, вопрос в том, допустима ли (и стандартна) следующая замена кода:

template <class Derived>
struct base {
  void foo() {
    // Generate a meaningful error if called
    (void)sizeof( Derived::foo_IS_MISSING );
  };
};

struct my_type : base<my_type> {
  void foo() {}; // required to compile.
};

struct your_type : base<your_type> {
  void foo() {}; // required to compile.
};

int main() {
  my_type my_obj;
  my_obj.foo(); // will fail if foo missing in derived class
}

Ответы [ 4 ]

4 голосов
/ 20 июля 2011

Суть этого шаблона, насколько я понимаю, в том, что вы можете передавать аргументы просто как template <typename T> base<T> &, а ваш интерфейс определяется (не виртуальными) функциями в base<T>. Если у вас нет интерфейса, который вы хотите определить (как вы предлагаете во второй части вашего вопроса), тогда вообще не нужно ничего из этого.

Обратите внимание, что вы не «навязываете» интерфейс, подобный чисто виртуальным функциям, а скорее обеспечивает интерфейс. Поскольку все решается во время компиляции, «навязывание» не является таким строгим требованием.

2 голосов
/ 20 июля 2011

Однако, похоже, что производный класс не требует определения для компиляции, поскольку он наследует один (, код компилируется нормально без определения my_type :: foo).

C ++ ленив: не будет пытаться сделать базу:: foo (), если вы на самом деле не используете его.Но если вы попытаетесь использовать его, он будет создан, а если это не удастся, ошибки компиляции будут появляться.Но в твоем случае база:: foo () может быть запущен просто отлично:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {};

void func() {
    my_type m;
    static_cast<base<my_type>& >(m).foo();
}

прекрасно скомпилируется.Когда компилятору предоставляется static_cast (this) -> foo (), он пытается найти функцию foo (), которая доступна в my_type.И есть один: он называется базовым:: foo (), который является открытым от публично унаследованного класса.так база:: foo () вызывает базу:: foo (), и вы получите бесконечную рекурсию.

2 голосов
/ 20 июля 2011

В вашем коде замены вы не можете «полиморфно» позвонить foo на base<T>.

0 голосов
/ 20 июля 2011

Нет, представьте себе следующую ситуацию:

template <typename T>
void bar(base<T> obj) {
   obj.foo();
}

base<my_type> my_obj;
bar(my_obj);

Foo базы будет вызываться вместо my_type ...

Сделайте это, и вы получите сообщение об ошибке:

template <class Derived>
struct base {
  void foo() {
    sizeof(Derived::foo);
    static_cast<Derived *>(this)->foo();
  };
};

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

...