【问题标题】:C++ Templates polymorphism obstacleC++ 模板多态性障碍
【发布时间】:2013-01-14 15:38:53
【问题描述】:

界面:

template <class T>
class Interface{
    public:
    typedef T Units;
    virtual T get() = 0;
};

实施1:

class Implementation1: public Interface<float> {
    public:

    float get() {
       return 0.0f;
    }

};

实施2:

class Implementation2: public Interface<int> {
    public:

    int get() {
       return 0;
    }

};

容器(有错误):

class Container{
    private:
    Interface* floatGetter;
    int n;
    Timer::Units* array;

    public:
    Container(Interface* floatGetter, int n) {
        this->floatGetter= floatGetter;
        this->n = n;
        array = new Timer::Units[n];
    }

    ~Container() {

    }

};

更多细节,我有一个模板接口和一个从这个接口派生的没有模板的类。其他一些类接受派生类的一个对象,但它将对象作为接口(换句话说,依赖注入)。但是这个类中接口的类型是由接口实现定义的。如何在 C++ 中实现这个想法?

编辑1:

例子:

Interface<float> myInterface1 = new Implementation1();
Interface<int> myInterface2 = new Implementation2();
Container container1 = new Container(myInterface1, 10);
Container container2 = new Container(myInterface2, 10);

我需要容器从其实现中理解接口模板参数。

【问题讨论】:

    标签: c++ templates polymorphism


    【解决方案1】:

    好的,首先,在这里对问题进行解释。需要的是一个接口,它定义了一个虚拟方法,用于获取模板类型的值。由于我们想要的是一个接口,get 方法必须是虚拟的。另一方面,我们希望能够返回不同的类型,因此我们希望将其模板化。但是,虚拟方法不能被模板化,因为编译器不知道该方法的哪些实例应该包含在 vtable 中。

    一种解决方案是做问题中所做的事情,即模板化接口类。模板类型的一个重要属性是同一类的不同实例化是完全不同的类型。它们没有共同的基础,也不能相互转换。我们根本不能在常规函数中使用Interface&lt;Generic&gt; 指针,同时调用它们的get() 方法。考虑一下:接口模板类型的每个实例都有不同的 get() 方法签名。这意味着在调用该方法时,堆栈上必须发生不同的事情。如果它只有一个Interface&lt;Generic&gt; 指针,编译器怎么知道要调用哪个版本的get() 方法(如何为函数调用准备堆栈)。

    对于这个问题,我能想到两种通用的解决方案。

    1. 删除所有模板 mumbo-jumbo 并使 get() 方法返回一个类型擦除的对象,例如 boost::variant 或 boost::any。如果我在这里错了,请纠正我(*),但是 boost::variant 就像一个联合,它记住分配了哪种类型的联合,而 boost::any 就像一个 void *,但它记得它指向的是什么类型.该解决方案路径意味着两件事: a) 返回对象的类型将在运行时解析,并且在操作这些类型时会有一些开销。 b) Interface 的子类必须管理这些类型擦除的对象之一,使它们更加复杂。

    2. 将模板 mumbo-jumbo 发挥到极致,并始终在模板化上下文中引用接口对象,以便编译器在这些上下文的实例化期间生成正确的函数调用。我在下面给出了一个遵循这条路径的例子。该示例创建了一个容器,用于将不同类型的 Interface 对象保存在一起,同时启用对它们的模板化功能(通常称其为“访问者”是否正确?)。请注意,在该示例中,具有不同类型参数的接口对象实际上保存在该容器类中的不同 std::list 中,因此在运行时,无需解析它们的类型。

    免责声明:接下来的内容有点矫枉过正......

    以下是如何拥有具有不同模板参数的“接口”模板类的容器。我使用 std::list 来保留实例,但您可以更改它。

    #include<boost/fusion/container/vector.hpp>
    #include<boost/fusion/algorithm.hpp>
    #include<boost/mpl/transform.hpp>
    #include<boost/mpl/contains.hpp>
    #include<boost/utility/enable_if.hpp>
    #include<boost/type_traits/add_reference.hpp>
    #include<list>
    #include<algorithm>
    #include <iostream>
    
    using namespace boost;
    
    template <class T>
    class Interface{
        public:
        typedef T Units;
        virtual T get() = 0;
    };
    
    class Implementation1: public Interface<float> {
        public:
    
        float get() {
           return 0.0f;
        }
    
    };
    
    class Implementation2: public Interface<int> {
        public:
    
        int get() {
           return 5;
        }
    
    };
    
    template<class element>
    struct to_list {
        typedef std::list<Interface<element> *> type;
    };
    
    template<class elementVector>
    struct to_containers {
        typedef typename mpl::transform<elementVector,to_list<mpl::_1> >::type type;
    };
    
    class Container{
        typedef fusion::vector<int,float> AllowedTypes;
        typename to_containers<AllowedTypes>::type containers;
    
    public:
        template<class type> typename enable_if<mpl::contains<AllowedTypes,type>,void>::type 
        /*void*/ add(Interface< type/*included in AllowedTypes*/ > & floatGetter) {
            fusion::deref(fusion::find<typename to_list<type>::type >(containers))
                /*<type> container*/.push_back(&floatGetter);
        }
    
        template<class functional>
        void apply(functional f) {
            fusion::for_each(containers,applyFunctional<functional>(f));
        }
    
    private:
        template<class functional>
        struct applyFunctional {
            functional f;
            applyFunctional(functional f): f(f){}
            template<class T> void operator()(T & in) const {
                std::for_each(in.begin(), in.end(),f);
            }
        };
    
    };
    
    struct printValueFunctional {
        template<class element>
        void operator()(Interface<element> * in) const {
            std::cout<<"Hi, my value is:"<<in->get()<<"\n";
        }
    };
    
    int main() {
    
        Implementation1 impl1;
        Implementation2 impl2;
        Interface<float> &myInterface1 = impl1;
        Interface<int> &myInterface2 = impl2;
        Container container;
        container.add(myInterface1);
        container.add(myInterface2);
        container.apply(printValueFunctional());
        return 0;
    }
    

    输出是:

    Hi, my value is:5
    Hi, my value is:0
    

    嗯,这对于大多数应用程序来说确实是一个巨大的过度杀伤,但你要求它:)

    如果你只想要一个接口,可以返回不同的东西,你也可以考虑 boost.variant。上面的示例对于它使用的所有静态多态性来说确实很有价值。

    编辑:大卫指出了一些重要的事情,如果您出于某种原因假设其他情况,这可能是一个陷阱。这个容器并没有真正保持项目插入的顺序。您的函数调用的顺序可能不会按照插入项的顺序发生,即假设迭代将以“随机”顺序进行。

    (*) boost::variant 和 boost::any 正在讨论here

    【讨论】:

    • +1 用于元编程。我认为这不是解决问题的好方法,但值得代表:)
    • 谢谢 :) 我也不认为它是解决这个问题的好方法,但它只是表明模板元编程允许在没有类型擦除的情况下这样做。您还可以获得一个迭代速度非常快的混合容器。
    • 它并不是真正的混合容器(或者是吗?)...而是一种在内部容纳多个容器的类型。对我而言,不同之处在于不同的类型在内部仍然是分开的,即使你认为它们不是,这意味着在使用类型擦除时,你可以保持容器不变量(例如,插入的顺序在序列容器中),你不能用这种方法做同样的事情(老实说这只是一种预感,我已经阅读了代码,但还没有编译/尝试过)
    • 嗯,它看起来像一个混合容器,它的作用就像一个混合容器,而且闻起来也像一个。但我仍然明白你的意思,如果你扩展了所有模板实例化,所有由元函数产生的东西,这与一个接一个地编写真正的容器类并手动分别处理类型没有什么不同。这也是神奇之处,它相当于这样做,但没有代码重复......(也没有维护头痛)
    【解决方案2】:

    Interface 是一个模板,而不是一个类型。您的类中的变量应该是具有特定类型的模板的实例化,如:

    class Container {
        Interface<float> *floatGetter;
    

    对于构造函数的参数也是如此。

    旁注:你的析构函数应该释放你的类处理的资源。

    旁注 2:编写一种直接管理多个资源的类型非常困难,请考虑使用智能指针来保存数据。

    旁注 3:学习和使用初始化列表。

    【讨论】:

    • 你的 constructor 应该释放资源吗?
    • @jesse 感谢您发现错字...当然析构函数应该释放资源,而不是构造函数。
    • @itun 您现在要求的内容在简单的 C++ 中是不可行的,我认为您误解了模板是什么。模板确实定义了一个类型,但是它们的一个系列。 interface&lt;int&gt;interface&lt;float&gt; 完全无关。现在,有些事情可以做,但您需要在解决问题之前解释您的要求(如果可以的话)。选项的复杂性从使container 成为模板(简单,但如果您打算以多态方式使用不同的containers 可能只是推动问题)到实现一些缺少类型擦除......
    • 使用模板元编程,您实际上可以避免类型擦除,并且在同一个容器中仍然有不同的 interface 对象。这有点复杂,所以如果有人真的关心它,我会在单独的答案中给出一个完整的例子。
    • @enobayram “如果有人真正关心它,我将在单独的答案中给出一个完整的例子。” - 我在乎,这将非常有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-15
    • 2011-08-25
    • 2011-11-09
    • 2016-07-23
    • 1970-01-01
    相关资源
    最近更新 更多