我正在做一个包装程序,以便能够将将来的代码轻松移植到不同的后端渲染引擎。我们目前在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复制虚拟抽象实现的行为?
答案 0 :(得分:2)
您正在滥用面向对象的编程。
在语义上,AbstractBackend
是一个接口:合同。如果类Alice
继承自AbstractBackend
,则Alice
是 AbstractBackend
。不完全是AbstractBackend
。完全为AbstractBackend
。这是Liskov's substitution principle(SOLID的L)。
如果类Bob
和Charlie
部分实现了AbstractBackend
,则意味着您确实有两个合同:Interface1
和Interface2
:
Bob
实现(继承)Interface1
,Charlie
实现(继承)Interface2
,Alice
实现(继承)Interface1
和 Interface2
。CRTP再次可用,您的代码闻起来又香又新鲜,生活愉快。周末愉快。
答案 1 :(得分:1)
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>
。
毕竟,我们正在实施编译时的“动态” dipatch;为什么不在编译时评估错误?
void foo_impl() = delete;
void bar_impl() = delete;
现在,在编译时在代码中完成分派的那一刻,您将得到错误信息,而不必等到编译时。