将实现注入到单个多功能接口类 - 许多CRTP类?

5

如何创建多个类来作为接口类的实现者,同时尽可能避免虚表成本,仍然能够进行静态转换?

对于简单情况,可以像下面的示例一样实现。

示例

库代码 :-

class I{ //interface
    public: virtual void i1()=0;
};
template<class Derived>class Router : public I{ 
    public: virtual void  i1()final{   
        //in real case it is very complex, but in the core is calling :-
        static_cast<Derived*>(this)->u1(); 
    }
};

用户代码 :-

class User : public Router<User>{
    public: void u1(){ std::cout<<"hi"<<std::endl; }
};
int main() {
    User u;
    u.i1();   //<-- no v-table cost
    I* i=&u;
    i->i1();  //<-- has v-table cost (OK)
}

enter image description here

完整演示

问题

如何扩展以上功能以支持2个或更多路由? enter image description here

下面的代码无法编译,但这是我的梦想。(完整演示)。

库代码 :-

class I{ //interface
    public: virtual void i1()=0;
    public: virtual void i2()=0;
};
template<class Derived>class RouterI1U1 : public I{ 
    public: virtual void  i1()final{ static_cast<Derived*>(this)->u1(); }
};
template<class Derived>class RouterI1U2 : public I{ 
    public: virtual void  i1()final{ static_cast<Derived*>(this)->u2(); }
};
template<class Derived>class RouterI2U1 : public I{ 
    public: virtual void  i2()final{ static_cast<Derived*>(this)->u1(); }
};
template<class Derived>class RouterI2U2 : public I{ 
    public: virtual void  i2()final{ static_cast<Derived*>(this)->u2(); }
};

用户代码 :-

想要使用以上库的人,可以轻松地选择任何他想要的“路线”。

  • 派生自RouterI1U2<User>RouterI2U1<User>
  • 派生自RouterI1U1<User>RouterI2U2<User>
  • 派生自{RouterI1U1<User>RouterI1U2<User>}并手动实现i2(),或
  • 派生自{RouterI2U2<User>RouterI2U1<User>}并手动实现i1(),或
  • 手动实现i1()i2()

以下是一个例子。

class User : public RouterI1U2<User>,public RouterI2U1<User>{ 
    public: void u1(){ std::cout<<"hi1"<<std::endl; }
    public: void u2(){ std::cout<<"hi2"<<std::endl; }
};
int main() {
    User u;
    u.i1();   //<-- no v-table cost
    I* i=&u;
    i->i1();  //<-- has v-table cost (OK)
}

我不太好的解决方案

class I{ //interface
    public: virtual void i1()=0;
    public: virtual void i2()=0;
};

template<class Derived> class RouterI1U2_I2U1 : public I{ //group it
    public: virtual void  i1()final{ static_cast<Derived*>(this)->u2(); }
    public: virtual void  i2()final{ static_cast<Derived*>(this)->u1(); }
};
class User : public RouterI1U2_I2U1<User>{ 
    public: void u1(){ std::cout<<"hi1"<<std::endl; }
    public: void u2(){ std::cout<<"hi2"<<std::endl; }
};

它可以工作(演示),但提供的模块性较少。(低重用性)
我必须手动打包RouterI1U2RouterI2U1RouterI1U2_I2U1中。

2个回答

3

也许在你的情况下不适用,但对于其他读者可能会有用。

我建议您在这种特定情况下使用概念模型习语。其目的是将多态实现和这些类本身的实现分开成不同的部分。

在这里,I 成为一个多态包装器,包装了任何具有 i1i2 成员函数的类:

class I {
    // The interface is internal, invisible to outside
    // We use this as a type erasure technique and polymorphism
    struct Concept {
        virtual void i1() = 0;
        virtual void i2() = 0;
    };

    // The single implementation that directly
    // extend the interface is the model. T is the user class.
    // T must have i1 and i2 function, because we call them.
    template<typename T>
    struct Model : Concept {

        // The user class.
        // If you want, you can use a reference there if you
        // need references semantics with I
        T user;

        Model (T u) : user{std::move(u)} {}

        // The only implementation of i1 is to call i1 from the user class
        void i1() override {
            user.i1();
        }

        void i2() override {
            user.i2();
        }
    };

    // Or use a shared, or use SBO
    std::unique_ptr<Concept> concept;

public:
    // When we make an I, we must provide a user class.
    // If the user class had i1 and i2, it will compile.
    // If Model takes a reference, use a reference there too.
    template<typename T>
    I(T model) : concept{std::make_unique<Model<T>>(std::move(model))} {}

    void i1() {
        concept->i1();
    }

    void i2() {
        concept->i2();
    }
};

然后,提供实现的类变成了这样:
template<class Derived>
struct RouterI1U1 { // no Inheritance needed
    void i1() { static_cast<Derived*>(this)->u1(); }
};

template<class Derived>
struct RouterI1U2 { 
    void i1() { static_cast<Derived*>(this)->u2(); }
};

template<class Derived>
struct RouterI2U1 { 
    void i2() { static_cast<Derived*>(this)->u1(); }
};

template<class Derived>
struct RouterI2U2 { 
    void i2() { static_cast<Derived*>(this)->u2(); }
};

由于这些 i1i2 只需要“存在”即可适应 Model<T> 类,因此不需要覆盖,也就不需要虚继承。

在使用时,它的效果如下:

struct User : RouterI2U2<User> {
    void i1() {}
    void u2() {}
};

正如您所看到的,我们没有任何虚方法。多态是 I 的实现细节。由于这个类具有所有所需的成员函数,因此 I 类将允许它。

而且使用类 I 也非常简单。假设 User2 是另一个符合 I 要求的用户类:

User2 user2;

user2.i1(); // no vtable, so no vtable overhead possible

I myI{user2}; // works!

myI.i2(); // calls u2, with vtable

std::vector<I> v;

v.emplace_back(User2{});
v.emplace_back(User{}); // simple heh?

以下是如何删除路由器类,并使用“或”样式接口来实现此操作。我的意思是一种接口,允许您实现某些内容其他内容。

Model<T>类中,您可以检查是否存在i1i2。如果不存在,您可以提供调用u1u2的实现。

我们首先创建类型特征,以告诉我们特定类型T是否具有成员函数i1i2

template<typename...>
using void_t = void;

template<typename, typename>
struct has_i1 : std::false_type {};

template<typename T>
struct has_i1<T, void_t<decltype(std::declval<T>().i1())>> : std::true_type {};

template<typename, typename>
struct has_i2 : std::false_type {};

template<typename T>
struct has_i2<T, void_t<decltype(std::declval<T>().i2())>> : std::true_type {};

现在,如果i1i2不存在,我们可以更改我们的模型实现来调用u1u2

template<typename T>
struct Model : Concept {
    T user;

    Model(T u) : user{std::move(u)} {}


    void i1() override {
        i1_helper(user);
    }

    void i2() override {
        i2_helper(user);
    }

private:
    template<typename U>
    auto i1_helper(U& u) -> std::enable_if_t<has_i1<U>::value> {
        // Call i1 if has i1
        u.i1();
    }

    template<typename U>
    auto i1_helper(U& u) -> std::enable_if_t<!has_i1<U>::value> {
        // Call u1 if has not i1
        u.u1();
    }

    template<typename U>
    auto i2_helper(U& u) -> std::enable_if_t<has_i2<U>::value> {
        // Call i2 if has i2
        u.i2();
    }

    template<typename U>
    auto i2_helper(U& u) -> std::enable_if_t<!has_i2<U>::value> {
        // Call u2 if has not i2
        u.u2();
    }
};

现在,您的用户类是最简单的。
struct User1 {
    void i1() {}
    void i2() {}
};

struct User2 {
    void i1() {}
    void u2() {}
};

struct User3 {
    void u1() {}
    void i2() {}
};

 struct User4 {
    void u1() {}
    void u2() {}
};

1
确实很有趣。采用这种方法,多重继承非常干净。我感觉就像手工制作自定义虚拟继承一样,因此更加可控。所有肮脏的工作都被移到一个地方,因此更易于维护。谢谢。 - cppBeginner
CRTP非常灵活,这不是您唯一的选择。如果我记得正确,有几种方法可以避免所有void i1() { static_cast<Derived*>(this)->u1(); }的重复。在我的活动项目中,我有一个bool update(Updatable<T> &u),它调用了static_cast-ed u的update方法,而Updatable<T>只是实现update()承诺。如果这个承诺没有被遵守,就会导致替换失败,并且您可以决定默认候选项或编译器错误更合适。一旦您掌握了它,请尝试嵌套/链接/common_type/等等! - John P

1
使用虚拟继承。
template<class Derived>class RouterI1U1 : public virtual I{ 

etc.使代码可以编译。


它有效(演示)。... u.i1(); 仍然不会有虚表成本,而 i->i1(); 将会有相同级别的虚表成本吗?(赞扬伟大的 n.m.) - cppBeginner
哦,这样的话,我不能使用 User* uPtr=static_cast<User*>(i);。... 我从未提过我想要它。我可能不需要它。 :) - cppBeginner
1
是的,您需要使用动态转换。 - n. m.
1
关于成本,我相信 u.i1() 仍然不会产生 vtable 成本,而 i->i1() 可能会因为 thunk 而产生稍微更高的成本。 - n. m.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接