我正在通过其C ++ SDK为应用程序编写插件。机制相当简单。插件通过预定义的接口提供其功能。这是通过让每个接口从一个实现类继承服务器类来完成的,该接口包含纯虚函数或具有默认实现的非纯函数。
这非常实用,因为SDK客户端只需要覆盖插件所需的那些方法和/或为没有默认值的(稀有)方法提供实现。
让我烦恼的是,编译时所有内容都是已知的。与运行时多态性相关联的虚函数表和机制仅用于提供默认实现 我试图在保持方便的同时消除这种开销。
作为一个(非常人为的)示例,假设我有几个服务器呈现单个接口(名为Blah),其中只包含一个没有默认实现的方法。
// SDK header
struct OldImpl_Blah {
virtual ~OldImpl_Blah() =default;
virtual int mult(int) =0;
};
// plugin source
class OldServer3 : public OldImpl_Blah {
public:
int mult(int i) override { return 3 * i; }
};
class OldServer5 : public OldImpl_Blah {
public:
int mult(int i) override { return 5 * i; }
};
对于纯虚函数,直接CRTP工作正常。
// SDK header
template <typename T>
struct NewImpl_Blah {
int mult(int i) { return static_cast<T*>(this)->mult(i); }
};
// plugin source
class NewServer3 : public NewImpl_Blah<NewServer3> {
public:
int mult(int i) { return 3 * i; }
};
class NewServer5 : public NewImpl_Blah<NewServer5> {
public:
int mult(int i) { return 5 * i; }
};
问题在于非 -pure虚函数,即当该方法有默认实现时。
// SDK header
struct OldImpl_Blah {
virtual ~OldImpl_Blah() =default;
virtual int mult(int i) { return i; } // default
};
// plugin source
class OldServer3 : public OldImpl_Blah {
public:
int mult(int i) override { return 3 * i; }
};
class OldServer5 : public OldImpl_Blah {
public:
int mult(int i) override { return 5 * i; }
};
我试图将CRTP与某些表达SFINAE欺骗结合起来并失败
我想我需要的是某种代码调度,其中基类可以提供默认实现,也可以将其参数转发给派生类中的实现(如果存在)。
问题似乎是调度应该依赖于基类中编译器尚不可用的信息。
一个简单的解决方案就是删除代码中的virtual
和override
个关键字。但是编译器不会检查函数签名是否匹配
这种情况有一些众所周知的模式吗?我问的是可能的吗?
(请使用小词,因为我对模板的专业知识有点偏轻。谢谢。)
答案 0 :(得分:3)
与往常一样,另一层间接是解决方案。在这种特殊情况下,它是众所周知的公共非虚拟函数技术,它们调用私有或受保护的虚函数。它有自己的用途,与此处讨论的内容无关,所以无论如何都要检查它。通常它的工作原理如下:
struct OldImpl_Blah {
piblic:
virtual ~OldImpl_Blah() = default;
int mult(int i) { return mult_impl(i); }
protected:
virtual int mult_impl(int i) { return i; }
};
// plugin source
class OldServer3 : public OldImpl_Blah {
protected:
int mult_impl(int i) override { return 3 * i; }
};
使用CRTP,它完全相同:
template <class T>
struct OldImpl_Blah {
piblic:
virtual ~OldImpl_Blah() = default;
int mult(int i) { return static_cast<T*>(this)->mult_impl(i); }
protected:
virtual int mult_impl(int i) { return i; }
};
// plugin source
class OldServer3 : public OldImpl_Blah<OldServer3> {
protected:
int mult_impl(int i) override { return 3 * i; }
};
免责声明:据说CRTP消除虚拟呼叫开销,需要函数为virtual
。我不知道CRTP在保留函数virtual
时是否有任何性能优势。
答案 1 :(得分:3)
说实话,我不确定我是否使用以下代码,但我认为它符合OP的要求。
这是一个极小的工作示例:
#include<iostream>
#include<utility>
template<class D>
struct B {
template <typename T>
struct hasFoo {
template<typename C>
static std::true_type check(decltype(&C::foo));
template<typename>
static std::false_type check(...);
static const bool value = decltype(check<T>(0))::value;
};
int foo() {
return B::foo<D>(0, this);
}
private:
template<class T>
static auto foo(int, B* p) -> typename std::enable_if<hasFoo<T>::value, int>::type {
std::cout << "D::foo" << std::endl;
return static_cast<T*>(p)->foo();
}
template<class T>
static auto foo(char, B*) -> typename std::enable_if<not hasFoo<T>::value, int>::type {
std::cout << "B::foo" << std::endl;
return 42;
}
};
struct A: B<A> { };
struct C: B<C> {
int foo() {
std::cout << "C::foo" << std::endl;
return 0;
}
};
int main() {
A a;
a.foo();
std::cout << "---" << std::endl;
B<A> *ba = new A;
ba->foo();
std::cout << "---" << std::endl;
C c;
c.foo();
std::cout << "---" << std::endl;
B<C> *bc = new C;
bc->foo();
}
如果我做得对,没有虚拟方法,但无论您使用的是基类还是派生类,都会调用foo
的正确实现。
当然,它是围绕CRTP习语而设计的。
我知道,成员探测器课程远远不够好 无论如何,这对于问题来说已经足够了,所以......
答案 2 :(得分:3)
考虑使用政策设计之类的东西:
struct DefaultMult {
int mult(int i) { return i; }
};
// SDK header
template <typename MultPolicy = DefaultMult>
struct NewImpl_Blah {
int mult(int i) { return multPolicy.mult(i); }
private:
MultPolicy multPolicy;
};
// plugin source
class NewServer3 {
public:
int mult(int i) { return 3 * i; }
};
class NewServer5 {
public:
int mult(int i) { return 5 * i; }
};
void client() {
NewImpl_Blah<NewServer5> myServer;
}
另请注意,理论上使用final
关键字和override
使编译器能够比vtable方法更优化地发送。如果您在第一次实施中使用final
关键字,我希望现代编译器能够进行优化。
一些有用的参考:
答案 3 :(得分:0)
我相信,我明白你要做什么。如果我的理解是正确的,那就无法完成。
逻辑上,您希望mult
中有Base
来检查子结构中是否存在mult
- 如果存在,请调用它,如果不存在,请提供一些默认实现。这里的缺陷是在子类中总是为mult
- 因为它将继承从Base检查mult
的实现。不可避免。
解决方案是在子类中以不同的方式命名函数,并在基础中检查是否存在不同命名的函数 - 并调用它。这是一件简单的事情,请告诉我您是否喜欢这个例子。但是,当然,你会在这里失去覆盖之美。