用CRTP替换非纯虚函数 我通过C++的SDK编写了一个应用程序的插件。机制相当简单。插件通过预定义的接口提供其功能。这是通过让服务器类从每个接口的一个实现类继承来实现的,该接口包含纯虚拟函数或具有默认实现的非纯函数。 这是非常实用的,因为SDK客户端只需覆盖插件所需的那些方法和/或为(罕见的)方法提供一个无默认值的实现

用CRTP替换非纯虚函数 我通过C++的SDK编写了一个应用程序的插件。机制相当简单。插件通过预定义的接口提供其功能。这是通过让服务器类从每个接口的一个实现类继承来实现的,该接口包含纯虚拟函数或具有默认实现的非纯函数。 这是非常实用的,因为SDK客户端只需覆盖插件所需的那些方法和/或为(罕见的)方法提供一个无默认值的实现,c++,inheritance,virtual,crtp,C++,Inheritance,Virtual,Crtp,一直困扰我的是在编译时所有的东西都是已知的。与运行时多态性相关的虚拟函数表和机制仅用于提供默认实现。 我试图在保持便利性的同时消除这种开销 作为一个(非常做作的)示例,假设我有两个服务器提供一个接口(名为Blah),该接口只包含一个方法,没有默认实现 // SDK header struct OldImpl_Blah { virtual ~OldImpl_Blah() =default; virtual int mult(int) =0; }; // plugin source

一直困扰我的是在编译时所有的东西都是已知的。与运行时多态性相关的虚拟函数表和机制仅用于提供默认实现。
我试图在保持便利性的同时消除这种开销

作为一个(非常做作的)示例,假设我有两个服务器提供一个接口(名为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; }
};
我试图将CRTP与一些表达结合起来,但失败了。
我想我需要的是某种代码分派,其中基类要么提供默认实现,要么将其参数转发给派生类中的实现(如果存在)。
问题似乎是分派应该依赖基类中编译器尚不可用的信息


一个简单的解决方案是删除代码中的
virtual
override
关键字。但是编译器不会检查函数签名是否匹配。
这种情况有什么众所周知的模式吗?我所要求的可能吗


(请使用小词,因为我在模板方面的专业知识有点浅薄。谢谢。)

我相信,我理解您的意图。如果我的理解是正确的,那是不可能的

从逻辑上讲,您希望在
Base
中使用
mult
来检查子结构中是否存在
mult
,如果存在,则调用它,如果不存在,则提供一些默认实现。这里的缺陷是子类中始终存在be
mult
,因为它将从基继承检查
mult
的实现。不可避免地


解决方案是在子类中对函数进行不同的命名,并在基类中检查是否存在不同命名的函数,然后调用它。这是一件简单的事情,如果你喜欢这个例子,请告诉我。但是,当然,您将失去这里的覆盖之美。

一如既往,另一个间接层次是解决方案。在这种特殊情况下,公共非虚拟函数调用私有或受保护的虚拟函数是众所周知的技术。它有自己的用途,与这里讨论的内容无关,所以不管怎样都可以查看它。通常它是这样工作的:

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; }
};
模板
结构OldImpl__Blah{
piblic:
virtual~OldImpl_Blah()=默认值;
intmult(inti){return static_cast(this)->mult_impl(i)}
受保护的:
虚拟整数mult_impl(整数i){return i;}
};
//插件源
类OldServer3:公共OldImpl__Blah{
受保护的:
int mult_impl(int i)重写{return 3*i;}
};

免责声明:据说CRTP消除了nit要求函数为
虚拟的虚拟呼叫开销。我不知道当函数保持虚拟时,CRTP是否有任何性能优势。老实说,我不确定我是否会使用以下代码,但我认为它符合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();
}
#包括
#包括
模板
结构B{
模板
结构hasFoo{
模板
静态std::true_类型检查(decltype(&C::foo));
模板
静态标准::假_类型检查(…);
静态常量布尔值=decltype(检查(0))::值;
};
int foo(){
返回B::foo(0,this);
}
私人:
模板
静态自动foo(int,B*p)->typename std::enable\u if::type{
std::cout typename std::enable_if::type{

std::cout考虑使用类似策略设计的方法:

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;
}
struct DefaultMult{
int mult(int i){return i;}
};
//SDK头
模板
结构NewImpl_Blah{
int mult(int i){return mult policy.mult(i);}
私人:
多政策多政策;
};
//插件源
类NewServer3{
公众:
int mult(int i){返回3*i;}
};
类NewServer5{
公众:
int mult(int i){返回5*i;}
};
无效客户端(){
NewImpl_Blah myServer;
}
另外请注意,理论上使用
final
关键字和
override
可以使编译器比vtable方法更优化地分派。如果在第一次实现中使用
final
关键字,我希望现代编译器能够进行优化

// 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; }
};
一些有用的参考资料:

  • 有关基于策略的设计的更多信息,请观看视频或阅读Andrei Alexandrescu的书/文章

你可以编译什么版本的C++?“编译器不会检查函数签名是否匹配”。你能通过提供一个代码示例来说明这可能会产生什么不良影响吗?@AndyG:我使用的是VS 2015 update 1。这是C++14吗?@n.m:我不确定我是否理解你的问题。例如,如果用户在名称中键入了一个错别字,它将创建一个新函数。CRTP没有“override”关键字的等价物(表示希望隐藏具有相同签名的基类的非虚拟成员函数)。这就是你要问的吗?我相信,OP试图完全避免虚拟调用。在我看来,将CRTP与虚拟调用结合起来是一件令人厌恶的事情。@SergeyA我相信OP希望保持函数的虚拟性,但相信CRTP将消除开销。我不知道是否是这样(或者