c++ - 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 复制虚拟抽象实现的行为?
解决方案
您正在滥用面向对象的编程。
从语义上讲,AbstractBackend
是一个接口:一个契约。如果一个类Alice
继承自AbstractBackend
,那么 anAlice
就是一个 AbstractBackend
。不是部分的AbstractBackend
。完全一个AbstractBackend
. 这就是Liskov 的替换原则(SOLID的 L )。
如果类Bob
和Charlie
部分实现AbstractBackend
,这意味着你真的有两个合同:Interface1
和Interface2
:
Bob
实现(继承)Interface1
,Charlie
实现(继承)Interface2
,Alice
实现(继承)Interface1
和Interface2
.
CRTP 又可以使用了,你的代码闻起来又香又新鲜,生活很愉快。周末愉快。
推荐阅读
- angular - 我如何在我正在调度的动作中传递 answerId,而 answerId 是作为响应它在 arrayobject 中的一个数组对象?
- python - 如何使用 FastAPI 在 TortoiseORM 上建立关系
- swift - SwiftUI TextField 绑定到一个简单的模型(非类结构)
- javascript - 使用innerHTML迭代时无法将对象作为参数传递给onClick
- php - 根据实时下拉列表过滤数据值
- python - 在 Bokeh 中使用 TextInput 字段进行过滤
- css - 当某个部分在 Gatsby 的同一页面上处于活动状态时,导航栏上的自定义样式
- python - 在 Python 中通过字典(键和值)迭代字符串列表
- react-native - react native react-native-svg-transformer 得到错误反应未定义
- javascript - 使用角度将图像从ckeditor4上传到firebase