首页 > 解决方案 > CRTP 中未实现的派生函数

问题描述

我正在制作一个包装器,以便能够轻松地将未来的代码移植到不同的后端渲染引擎。我们目前在 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 复制虚拟抽象实现的行为?

标签: c++oopc++11abstractcrtp

解决方案


您正在滥用面向对象的编程。

从语义上讲,AbstractBackend是一个接口:一个契约。如果一个类Alice继承自AbstractBackend,那么 anAlice 就是一个 AbstractBackend。不是部分的AbstractBackend。完全一个AbstractBackend. 这就是Liskov 的替换原则SOLID的 L )。

如果类BobCharlie部分实现AbstractBackend,这意味着你真的有两个合同Interface1Interface2

  • Bob实现(继承)Interface1
  • Charlie实现(继承)Interface2
  • Alice实现(继承)Interface1 Interface2.

CRTP 又可以使用了,你的代码闻起来又香又新鲜,生活很愉快。周末愉快。


推荐阅读