【问题标题】:inject implementation to a single multi-function interface class - many CRTP classes?将实现注入单个多功能接口类 - 许多 CRTP 类?
【发布时间】:2017-08-22 03:38:35
【问题描述】:

如何创建许多类来充当接口类的实现者,同时尽可能避免 v-table 开销,并且仍然启用对接口的静态转换?

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

示例

库代码 :-

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)
}

Full demo

问题

如何扩展上述功能以支持 2 条或更多路线?

以下代码无法编译,但它描绘了我的梦想。 (full demo)。

库代码 :-

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&lt;User&gt;RouterI2U1&lt;User&gt;
  • 派生自RouterI1U1&lt;User&gt;RouterI2U2&lt;User&gt;
  • 派生自 {RouterI1U1&lt;User&gt;RouterI1U2&lt;User&gt;} 并使用 final 手动实现 i2()
  • 派生自 {RouterI2U2&lt;User&gt;RouterI2U1&lt;User&gt;} 并使用 final 手动实现 i1()
  • 使用 final 手动实现 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; }
};

它可以工作 (demo),但提供的模块化程度较低。 (低可重用性)
我必须手动将RouterI1U2RouterI2U1 打包到RouterI1U2_I2U1

【问题讨论】:

    标签: c++ interface c++14 crtp vtable


    【解决方案1】:

    它可能不适用于您的情况,但也可能对其他阅读问题有用。

    对于这种特殊情况,我建议您使用概念模型习语。这样做的目的是将多态性实现和这些类本身的实现分离到不同的部分。在这里,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&lt;T&gt; 类,因此不需要覆盖,因此不需要虚拟继承。

    使用时它实际上看起来像这样:

    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&lt;T&gt;类中,你可以检查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() {}
    };
    

    【讨论】:

    • 确实很有趣。使用这种方法,多重继承非常干净。感觉就像手工做一个自定义的虚拟继承,所以更可控。所有脏活都移到一个地方,因此更易于维护。谢谢。
    • CRTP 也非常灵活,这不是您唯一的选择。如果我没记错的话,有几种方法可以绕过所有 void i1() { static_cast&lt;Derived*&gt;(this)-&gt;u1(); } 重复。在我的活动项目中,我有bool update(Updatable&lt;T&gt; &amp;u) 调用static_cast-ed u 的update 方法,而Updatable&lt;T&gt; 只是实现update()promise。如果没有遵守该承诺,则会导致替换失败,您可以决定是默认候选错误还是编译器错误更合适。尝试嵌套/链接/common_type/等。一旦你得到它!
    【解决方案2】:

    使用虚拟继承。

    template<class Derived>class RouterI1U1 : public virtual I{ 
    

    等等。使代码可编译。

    【讨论】:

    • 它有效 (demo)。 .....u.i1(); 是否仍然没有 v-table 成本,i-&gt;i1(); 会遭受同样水平的 v-table 成本? (赞美伟大的n.m.)
    • 哦,这样,我不能User* uPtr=static_cast&lt;User*&gt;(i);。 ......我从来没有提到我想要它。我可能不需要它。 :)
    • 是的,你需要一个动态演员表。
    • 至于成本,我相信 u.i1() 仍然不会产生 vtable 成本,而 i->i1() 可能会因为 thunk 而产生略高的成本。
    猜你喜欢
    • 1970-01-01
    • 2020-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多