Неосуществленная производная функция в CRTP - PullRequest
0 голосов
/ 01 февраля 2019

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

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

#include <Windows.h>
#include <iostream>

struct AbstractBackend
{
  virtual ~AbstractBackend() = 0;

  virtual void foo()
  {
    throw "implementation missing: failed to override in derived class";
  }

  virtual void bar()
  {
    throw "implementation missing: failed to override in derived class";
  }
};

AbstractBackend::~AbstractBackend() {}

struct ConcreteBackendA : AbstractBackend
{
  int backendResource;

  ConcreteBackendA(int rsc) :
    backendResource(rsc)
  {}

  virtual void foo()
  {
    printf("executing ConcreteBackendA::foo!\n");
  }

  // ConcreteBackendA does not support "bar" feature
};

struct ConcreteBackendB : AbstractBackend
{
  HDC backendResource;

  ConcreteBackendB(HDC hdc) :
    backendResource(hdc)
  {}

  virtual void foo()
  {
    printf("executing ConcreteBackendB::foo!\n");
  }

  virtual void bar()
  {
    printf("executing ConcreteBackendB::bar!\n");
  }

};

struct FrontEnd
{
  AbstractBackend *backend;

  FrontEnd(int rsc) :
    backend(new ConcreteBackendA(rsc))
  {}

  FrontEnd(HDC hdc) :
    backend(new ConcreteBackendB(hdc))
  {}

  ~FrontEnd()
  {
    delete backend;
  }

  void foo()
  {
    backend->foo();
  }

  void bar()
  {
    backend->bar();
  }
};

int main()
{
  int rsc = 0;
  HDC hdc = 0;
  FrontEnd A(rsc);
  FrontEnd B(hdc);

  A.foo();
  A.bar(); // throws an error, A::bar is not a feature of this engine

  B.foo();
  B.bar();

  std::cin.get();
}

В этом примере AbstractBackend поддерживает две функции, foo и bar.ConcreteBackendA поддерживает только foo, bar - это функция, которую он не может поддерживать (возможно, что-то вроде Draw3dText), но это нормально.Пользователь может ловить исключения и двигаться дальше.Один небольшой недостаток - использование виртуальных функций.Я хотел бы развлечь мысль об использовании CRTP следующим образом:

#include <Windows.h>
#include <iostream>

template <class Derived>
struct AbstractBackend
{
  virtual ~AbstractBackend() = 0;

  void foo()
  {
    static_cast<Derived*>(this)->foo();
  }

  void bar()
  {
    static_cast<Derived*>(this)->bar();
  }
};

template <class Derived>
AbstractBackend<Derived>::~AbstractBackend() {}

struct ConcreteBackendA : AbstractBackend<ConcreteBackendA>
{
  int backendResource;

  ConcreteBackendA(int rsc) :
    backendResource(rsc)
  {}

  void foo()
  {
    printf("executing ConcreteBackendA::foo!\n");
  }

  // ConcreteBackendA does not support "bar" feature
};

struct ConcreteBackendB : AbstractBackend<ConcreteBackendB>
{
  HDC backendResource;

  ConcreteBackendB(HDC hdc) :
    backendResource(hdc)
  {}

  void foo()
  {
    printf("executing ConcreteBackendB::foo!\n");
  }

  void bar()
  {
    printf("executing ConcreteBackendB::bar!\n");
  }
};

template <class ConcreteBackend>
struct FrontEnd
{
  AbstractBackend<ConcreteBackend> *backend;

  FrontEnd(int rsc) :
    backend(new ConcreteBackendA(rsc))
  {}

  FrontEnd(HDC hdc) :
    backend(new ConcreteBackendB(hdc))
  {}

  ~FrontEnd()
  {
    delete backend;
  }

  void foo()
  {
    backend->foo();
  }

  void bar()
  {
    backend->bar();
  }
};

int main()
{
  int rsc = 0;
  HDC hdc = 0;
  FrontEnd<ConcreteBackendA> A(rsc);
  FrontEnd<ConcreteBackendB> B(hdc);

  A.foo();
  A.bar(); // no implementation: stack overflow

  B.foo();
  B.bar();

  std::cin.get();
}

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

Как можно реплицировать поведение виртуальной абстрактной реализации с CRTP?

Ответы [ 2 ]

0 голосов
/ 02 февраля 2019
template <class Derived>
struct AbstractBackend
{
  virtual ~AbstractBackend() = 0;

  void foo()
  {
    static_cast<Derived*>(this)->foo_impl();
  }

  void bar()
  {
    static_cast<Derived*>(this)->bar_impl();
  }

  void foo_impl()
  {
    throw "implementation missing: failed to override in derived class";
  }

  void bar_impl()
  {
    throw "implementation missing: failed to override in derived class";
  }

};

теперь у вас может быть реализация по умолчанию foo / bar.

Переопределенные производные классы foo_impl вместо foo.

Однако этот конкретныйиспользование плохой план;вы знаете во время компиляции, реализован ли данный AbstractBackend<D> или нет.

В конце концов, мы реализуем «динамический» диппатч во время компиляции;почему бы не оценить ошибку во время компиляции?

  void foo_impl() = delete;
  void bar_impl() = delete;

сейчас, в момент, когда отправка выполняется в вашем коде во время компиляции, вы получаете ошибку, вместо того, чтобы ждать до момента компиляции.

0 голосов
/ 01 февраля 2019

Вы злоупотребляете объектно-ориентированным программированием.

Семантически, AbstractBackend - это интерфейс: контракт.Если класс Alice наследуется от AbstractBackend, то Alice является AbstractBackend.Не частично AbstractBackend.Полностью AbstractBackend.Это принцип подстановки Лискова (L SOLID ).

Если классы Bob и Charlie частично реализуют AbstractBackend, это означает, что вы действительноиметь два контракта : Interface1 и Interface2:

  • Bob реализует (наследует) Interface1,
  • Charlie реализует (наследует) Interface2,
  • Alice реализует (наследует) Interface1 и Interface2.

CRTP снова можно использовать, ваш код пахнет хорошои свежо, жизнь приятна.Хороших выходных.

...