【问题标题】:C++ concept check vs inheritanceC++ 概念检查与继承
【发布时间】:2009-03-06 23:51:08
【问题描述】:

使用虚函数和 C++ 继承机制与使用模板和类似 boost 概念之间的关系是什么?

似乎有很多可能的重叠。也就是说,似乎有可能用任何一种方法来实现多态行为。那么,什么时候偏爱一个才有意义呢?

之所以提出这个问题,是因为我有一个模板化容器,其中容器本身具有层次关系。我想编写使用这些容器的算法,而不关心它是哪个特定容器。此外,某些算法会受益于知道模板类型满足某些概念(例如 Comparable)。

因此,一方面,我希望容器具有多态性。另一方面,如果我想正确实现一些算法,我仍然必须使用概念。初级开发人员要做什么?

【问题讨论】:

    标签: c++ templates polymorphism


    【解决方案1】:

    我认为概念是一种元界面。他们根据能力对类型进行分类。下一个 C++ 版本提供本地概念。直到我遇到 C++1x 的概念以及它们如何允许将不同但不相关的类型放在一起之前,我才理解它。假设你有一个Range 接口。您可以通过两种方式对其进行建模。一种是子类型关系

    class Range {
        virtual Iterator * begin() = 0;
        virtual Iterator * end() = 0;
    
        virtual size_t size() = 0;
    };
    

    当然,从它派生的每个类都实现 Range 接口并且可以与您的函数一起使用。但现在你看到它是有限的。数组呢?这也是一个范围!

    T t[N];
    
    begin() => t
    end() => t + size()
    size() => N
    

    遗憾的是,您不能从实现该接口的 Range 类派生数组。您需要一个额外的方法(重载)。那么第三方容器呢?您的库的用户可能希望将他们的容器与您的函数一起使用。但他不能改变他们容器的定义。在这里,概念开始发挥作用:

    auto concept Range<typename T> {
        typename iterator;
        iterator T::begin();
        iterator T::end();
        size_t T::size();
    }
    

    现在,您说一下如果T 具有适当的成员函数可以实现的某种类型的受支持操作。在您的库中,您将编写函数泛型。这允许您接受任何类型只要它支持所需的操作:

    template<Range R>
    void assign(R const& r) {
        ... iterate from r.begin() to r.end(). 
    }
    

    这是一种很好的可替代性Any 类型将符合遵循该概念的要求,而不仅仅是那些积极实现某些接口的类型。下一个 C++ 标准更进一步:它定义了一个 Container 概念,该概念将适合普通数组(通过定义某些类型如何适合某些概念的称为 concept map 的东西)和其他现有的 标准容器。

    之所以提出这个问题,是因为我有一个模板化容器,其中容器本身具有层级关系。我想编写使用这些容器的算法,而不关心它是哪个特定容器。此外,某些算法会受益于知道模板类型满足某些概念(例如 Comparable)。

    您实际上可以同时使用模板。你可以保持你的层次关系来共享代码,然后以通用的方式编写算法。例如,传达您的容器具有可比性。这就像实现了标准的随机访问/转发/输出/输入迭代器类别:

    // tag types for the comparator cagetory
    struct not_comparable { };
    struct basic_comparable : not_comparable { };
    
    template<typename T>
    class MyVector : public BasicContainer<T> {
        typedef basic_comparable comparator_kind;
    };
    
    /* Container concept */
    T::comparator_kind: comparator category
    

    实际上,这是一种合理简单的方法。现在您可以调用一个函数,它将转发到正确的实现。

    template<typename Container>
    void takesAdvantage(Container const& c) {
        takesAdvantageOfCompare(c, typename Container::comparator_kind());
    }
    
    // implementation for basic_comparable containers
    template<typename Container>
    void takesAdvantage(Container const& c, basic_comparable) {
        ...
    }
    
    // implementation for not_comparable containers
    template<typename Container>
    void takesAdvantage(Container const& c, not_comparable) {
        ...
    }
    

    实际上可以使用不同的技术来实现它。另一种方法是每次使用boost::enable_if 启用或禁用不同的实现。

    【讨论】:

    • C++1x?这是否意味着他们在这十年中放弃了发布新标准,还是您在谈论未来的 C++ 开发?
    • jpalecek,他们想在 2010 年发布它。我习惯称它为 c++1x :)
    • jpalecek,如果您有兴趣进一步阅读,请使用open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2800.pdf 并阅读第 14.9 章。在 13.1.6 中,您会找到容器概念。玩得开心:)
    • 我只关心标识符,C++1x 可能会建议您讨论下一个(超越 c++0x)标准。顺便说一句,容器概念在 23.1.6 中,而不是 13.1.6
    【解决方案2】:

    是的,这两种机制都可以实现多态行为。事实上,两者都称为多态。

    虚拟函数为您提供动态多态性(因为它是在运行时决定的),而模板为您提供静态多态性(一切都在编译时决定)。

    这也应该回答更喜欢哪个的问题。只要有可能,宁愿将工作转移到编译时。因此,当您可以摆脱它时,请使用模板来解决您的多态性需求。如果这不可行(因为您需要使用运行时类型信息,因为在编译时不知道确切的类型),请回退到动态多态。

    (当然,可能有其他原因偏爱其中一个。特别是,模板要求您将大量代码移动到头文件中,这可能有问题也可能没有问题,而且编译速度往往会受到影响,这也可能是也可能不是问题。)

    【讨论】:

      【解决方案3】:

      如果可以在编译时做出决定,请使用模板。否则使用继承和虚函数。

      【讨论】:

        【解决方案4】:

        在这种特定情况下,您可以执行类似的操作

        template<typename T>
        class ContainerBase{};
        
        template<typename T>
        class ContainerDerived : public ContainerBase<T> {};
        

        由于每种“容器”类型对于每种模板类型都是唯一的,因此每种容器类型的成员函数没有理由不能专门用于模板化类型的特征。

        【讨论】:

          【解决方案5】:

          作为编译时和运行时多态性之间差异的简单示例,请考虑以下代码:

          template<typename tType>
          struct compileTimePolymorphism
          { };
          
          // compile time polymorphism,
          // you can describe a behavior on some object type
          // through the template, but you cannot interchange 
          // the templates
          compileTimePolymorphism<int> l_intTemplate;
          compileTimePolymorphism<float> l_floatTemplate;
          compileTimePolymorphism *l_templatePointer; // ???? impossible
          
          struct A {};
          struct B : public A{};
          struct C : public A{};
          
          // runtime polymorphism 
          // you can interchange objects of different type
          // by treating them like the parent
          B l_B;
          C l_C:
          A *l_A = &l_B;
          l_A = &l_C;
          

          当一个对象的行为依赖于其他对象时,编译时多态性是一个很好的解决方案。当对象的行为需要改变时,运行时多态是必要的。

          这两者可以通过定义一个多态的模板来组合:

          template<typename tType>
          struct myContainer : public tType
          {};
          

          接下来的问题是容器的行为需要改变的地方(运行时多态性),以及行为取决于它包含的对象的地方(编译时多态性)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-07-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多