美文网首页C++ Templates
【C++ Templates(19)】模板与继承

【C++ Templates(19)】模板与继承

作者: downdemo | 来源:发表于2018-06-19 10:18 被阅读11次

    空基类优化(EBCO)

    布局原则

    • C++不允许类大小为0,比如数组元素为类,若类大小为0则数组大小也是0,这样会导致指针运算失效。虽然不能存在大小为0的类,但标准规定,空类为基类时,只要不与同一类型的另一个对象或子对象分配在同一地址,就不用分配任何空间
    #include <iostream>
    
    class Empty {
        using Int = int;  // type alias members don't make a class nonempty
    };
    
    class EmptyToo : public Empty {
    };
    
    class EmptyThree : public EmptyToo {
    };
    
    int main()
    {
        std::cout << "sizeof(Empty):      " << sizeof(Empty)
                  << '\n';
        std::cout << "sizeof(EmptyToo):   " << sizeof(EmptyToo)
                  << '\n';
        std::cout << "sizeof(EmptyThree): " << sizeof(EmptyThree)
                  << '\n';
    }
    
    • 如果编译器支持空基类优化,上述输出结果相同但均不为0,说明EmptyToo中的类Empty没有分配空间。带有优化空基类的空类大小也为0,这也是类EmptyThree和类Empty大小相同的原因,但在不支持EBCO的编译器上结果就截然不同
    Layout of EmptyThree by a compiler that implements the EBCO Layout of EmptyThree by a compiler that does not implement the EBCO
    • 在空基类优化下,下例依然输出相同的结果
    #include <iostream>
    
    class Empty {
        using Int = int;  // type alias members don't make a class nonempty
    };
    
    class EmptyToo : public Empty {
    };
    
    class NonEmpty : public Empty, public EmptyToo {
    };
    
    int main()
    {
        std::cout << "sizeof(Empty):    " << sizeof(Empty) << '\n';
        std::cout << "sizeof(EmptyToo): " << sizeof(EmptyToo) << '\n';
        std::cout << "sizeof(NonEmpty): " << sizeof(NonEmpty) << '\n';
    }
    
    • NonEmpty的基类Empty和EmptyToo不能分配到同一地址空间,否则EmptyToo的基类Empty会和NonEmpty的基类Empty撞在同一地址空间上,即不允许两个相同类型的子对象偏移量相同
    Layout of NonEmpty by a compiler that implements the EBCO
    • 对空基类优化限制的根本原因在于,需要能比较两个指针是否指向同一对象,因为指针总是用地址作内部表示,所以必须保证两个不同的指针值对应两个不同的对象。实际应用中许多类都是继承自一组定义公共typedefs的基类,当这些类作为子对象出现在同一对象中便会出现问题,此时优化应被禁止

    成员作为基类

    • 对于数据成员则不存在类似EBCO的技术,否则指向成员的指针就会有问题,但模板参数可能是空类
    template <typename T1, typename T2>
    class MyClass {
    private:
        T1 a;
        T2 b;
        ...
    };
    
    • T1或T2都可能是空类,这样MyClass<T1, T2>就不能得到最优布局,把模板参数作为基类可以解决这个问题
    template <typename T1, typename T2>
    class MyClass : private T1, private T2 {
    };
    
    • 但T1和T2不一定是类,或者T1和T2是相同类型。如果已知一个模板参数类型为类,另一个类型不是空类,更可行的办法是利用EBCO把可能为空的类型参数与这个成员合并起来
    template <typename CustomClass>
    class Optimizable {
    private:
        CustomClass info; // might be empty
        void* storage;
        ...
    };
    
    • 改写为
    template <typename CustomClass>
    class Optimizable {
    private:
        BaseMemberPair<CustomClass, void*> info_and_storage;
        ...
    };
    
    • BaseMemberPair的实现如下,封装在BaseMemberPair中的数据成员通过成员函数first()和second()访问
    // inherit/basememberpair.hpp
    
    #ifndef BASE_MEMBER_PAIR_HPP
    #define BASE_MEMBER_PAIR_HPP
    
    template <typename Base, typename Member>
    class BaseMemberPair : private Base {
      private:
        Member member;
      public:
        // constructor
        BaseMemberPair (Base const & b, Member const & m)
         : Base(b), member(m) {
        }
    
        // access base class data via first()
        Base const& first() const {
            return (Base const&)*this;
        }
    
        Base& first() {
            return (Base&)*this;
        }
    
        // access member data via second()
        Member const& second() const {
            return this->member;
        }
        Member& second() {
            return this->member;
        }
    };
    
    #endif // BASE_MEMBER_PAIR_HPP
    

    奇异递归模板模式(CRTP)

    • CRTP代表类实现技术中的一种通用模式,即派生类本身作为模板参数传递给基类,最简单的情形如下
    template <typename Derived>
    class CuriousBase {
        ...
    };
    
    class Curious : public CuriousBase<Curious> {
        ...
    };
    
    • 稍微复杂一些
    template <typename Derived>
    class CuriousBase {
        ...
    };
    
    template <typename T>
    class CuriousTemplate : public CuriousBase<CuriousTemplate<T>> {
        ...
    };
    
    • 还可以使用模板的模板参数
    template <template<typename> class Derived>
        class MoreCuriousBase {
        ...
    };
    
    template <typename T>
        class MoreCurious : public MoreCuriousBase<MoreCurious> {
        ...
    };
    
    • CRTP的一个简单应用是记录某个类的对象构造的总个数,计算对象个数只需要引入一个静态成员,在构造和析构时对其递增和递减
    // inherit/objectcounter.hpp
    
    #include <cstddef>
    
    template <typename CountedType>
    class ObjectCounter {
    private:
        static std::size_t count;    // number of existing objects
        // before C++17, inline had to be define outside the class template
        // since C++17 could write
        // inline static std::size_t count = 0;
    
    protected:
        // default constructor
        ObjectCounter() { 
            ++count;
        }
    
        // copy constructor
        ObjectCounter (ObjectCounter<CountedType> const&) {
            ++count; 
        }
    
        // move constructor
        ObjectCounter (ObjectCounter<CountedType> &&) {
            ++count; 
        }
    
        // destructor
        ~ObjectCounter() { 
            --count;
        }
    
    public:
        // return number of existing objects:
        static std::size_t live() { 
            return count; 
        }
    };
    
    // initialize counter with zero
    template <typename CountedType>
    size_t ObjectCounter<CountedType>::count = 0;
    
    • 想要计算某个类的对象个数,只要让该类从模板ObjectCounter派生
    // inherit/testcounter.cpp
    
    #include "objectcounter.hpp"
    #include <iostream>
    
    template <typename CharT>
    class MyString : public ObjectCounter<MyString<CharT>> {
      //...
    };
    
    int main()
    {
        MyString<char> s1, s2;
        MyString<wchar_t> ws;
    
        std::cout << "num of MyString<char>:    " 
                  << MyString<char>::live() << ‘\n';
        std::cout << "num of MyString<wchar_t>: " 
                  << ws.live() << '\n';
    }
    
    • 一般CRTP用于仅能用作成员函数的接口(如构造函数、析构函数和下标运算operator[])实现中的提取

    Barton-Nackman Trick

    • 在1994年,John J. Barton和Lee R. Nackman给出了一项名为限制的模板扩展(restricted template expansion)技术,这项技术的部分动机是,那时大多数编译器不能支持函数模板重载,也没有实现命名空间
    • 为类模板Array定义operator==,一种实现方法是把运算符声明为类模板成员,然而这样该运算符的第一个实参(绑定为this指针)和第二个实参转型规则可能不同,无法保证实参的对称性。另一种实现是把运算符声明为一个命名空间作用域函数,如下
    template<typename T>
    class Array {
    public:
        ...
    };
    
    template<typename T>
    bool operator== (Array<T> const& a, Array<T> const& b)
    {
        ...
    }
    
    • 如果函数模板不能重载,则在此作用域中就不能声明其他operator==模板,导致不能为其他类模板提供这个运算符模板。Barton 和Nackman把运算符作为友元函数定义在类内部,解决了这个问题
    template<typename T>
    class Array {
        static bool areEqual(Array<T> const& a, Array<T> const& b);
    public:
        ...
        friend bool operator== (Array<T> const& a, Array<T> const& b)
        {
            return areEqual(a, b);
        }
    };
    
    • 假如用float实例化Array,友元也被作为实例化结果声明,然而函数本身不是模板的实例化,而是一个非模板函数,只是借用了实例化的副作用插入到全局作用域中。因为是非模板函数,所以可以重载。这个技术之所以被称为限制的模板扩展,就是因为它避免了使用模板operator=(T, T)
    • operator== (Array<T> const&, Array<T> const&)定义在类内,隐式内联,所以可以把实现委托给不需要是内联的静态成员函数areEqual
    • 友元函数定义名称查找在1994年已经改变了,因此Barton-Nackman trick在标准C++中已经没用了。在它发明时,友元声明在类模板最近的作用域可见,模板被一个名为友元名称插入的过程实例化。而标准C++通过ADL查找友元函数声明。这意味着,函数调用实参必须和包含友元函数的类具有关联,否则就不能找到该友元函数,即使调用实参所关联的某个类能转换为包含友元的类,比如
    // inherit/wrapper.cpp
    class S {
    };
    
    template<typename T>
    class Wrapper {
    private:
        T object;
    public:
        Wrapper(T obj) : object(obj) { // implicit conversion from T to Wrapper<T>
        }
        friend void foo(Wrapper<T> const&) {
        }
    };
    
    int main()
    {
        S s;
        Wrapper<S> w(s);
        foo(w); // OK: Wrapper<S> is a class associated with w
        foo(s); // ERROR: Wrapper<S> is not associated with s
    }
    
    • 这里调用foo(w)有效,因为foo()是一个声明在Wrapper<S>内部的友元函数,而Wrapper<S>和实参w是相关的。而调用foo(s)时,友元函数声明foo(Wrapper<S> const&)不可见,因为Wrapper<S>和实参s的类型S是不相关联的。即使存在S到Wrapper<S>的隐式转换也不会被考虑,因为编译器不能首先找到候选函数foo,也就不会考虑foo参数要进行的转型
    • 现代C++中,比器普通的函数模板定义,类模板内定义友元函数的唯一优点是语法上写法简单一些:友元函数定义可以访问enclosing class的private和protected成员,而不需要重申所有的模板参数。然而,友元函数定义结合CRTP时十分有用,即接下来要阐述的operator实现

    operator实现

    • 当实现一个提供重载运算符的类时,提供大量不同或相关的运算符重载很常见。比如实现operator==的类可能还要实现!=,实现<的类可能还要实现>,<=,>=。有趣的是很多情况下,这些运算符只有一个定义,其他运算符可以由这一个来实现,比如类X的!=可以用==实现
    bool operator!= (X const& x1, X const& x2) {
        return !(x1 == x2);
    }
    
    • 给出大量有与!=相似定义的类型,很容易想到把他们归纳成模板
    template<typename T>
    bool operator!= (T const& x1, T const& x2) {
        return !(x1 == x2);
    }
    
    • 事实上,标准库<utility>的一部分就有类似的定义。然而,这些定义(!=,>,<=,>=)在标准化时被降级到了命名空间std::rel_ops,因为他们被确定在命名空间std中可用将造成问题
    • 确实,让这些定义可见能让任何有!=的类型出现(可能实例化失败),且运算符将总能精确匹配实参。不过第一个问题可以通过SFINAE克服,!=定义只会对适合==的类型实例化。第二个问题仍存在:通用的!=定义将优先于用户需要提供的定义,比如一个派生类到基类的转换将出现意外
    • 一个基于CRTP对这些运算符的构建,允许类选择进入通用的运算符定义,从而提供代码复用的好处,而没有过度通用的运算符的副作用
    // inherit/equalitycomparable.cpp
    template<typename Derived>
    class EqualityComparable {
    public:
        friend bool operator!= (Derived const& x1, Derived const& x2)
        {
            return !(x1 == x2);
        }
    };
    
    class X : public EqualityComparable<X> {
    public:
        friend bool operator== (X const& x1, X const& x2) {
        // implement logic for comparing two objects of type X
        }
    };
    
    int main()
    {
        X x1, x2;
        if (x1 != x2) { }
    }
    
    • 这里结合了CRTP与Barton-Nackman trick,EqualityComparable<>使用CRTP为派生类提供一个基于派生类operator==定义的operator!=,使用Barton-Nackman trick通过一个友元函数定义提供了定义
    • 当把行为转换而基类,而又要保留最终的派生类的定义时,CRTP是有用的。结合Barton-Nackman trick,CRTP可以为许多运算符提供基于一些权威运算符的通用定义,这些属性使得结合Barton-Nackman的CRTP成为C++模板库作者最喜爱的技术

    Facade

    • 使用CRTP和Barton-Nackman trick定义运算符是一个很方便的捷径,把这个思路扩展开,CRTP基类可以根据由CRTP派生类暴露的小得多的接口定义大部分public接口,这个模式称为facade模式。当定义需要满足一些现有接口(数字类型、迭代器、容器等)的新类型时,facade是特别有用的
    • 为了阐述facade模式,首先实现一个迭代器facade,它能大大简化写一个遵循标准库要求的迭代器的过程。迭代器类型(尤其是随机访问迭代器)需要的接口十分大,下面的类模板IteratorFacade的骨架简单描述了迭代器接口的要求
    // inherit/iteratorfacadeskel.hpp
    
    template<typename Derived, typename Value,
        typename Category, typename Reference = Value&, 
        typename Distance = std::ptrdiff_t>
    class IteratorFacade {
    public:
        using value_type = typename std::remove_const<Value>::type;
        using reference = Reference;
        using pointer = Value*;
        using difference_type = Distance;
        using iterator_category = Category;
    
        // input iterator interface:
        reference operator *() const { ... }
        pointer operator ->() const { ... }
        Derived& operator ++() { ... }
        Derived operator ++(int) { ... }
        friend bool operator== (IteratorFacade const& lhs,
        IteratorFacade const& rhs) { ... }
        ...
    
        // bidirectional iterator interface:
        Derived& operator --() { ... }
        Derived operator --(int) { ... }
    
        // random access iterator interface:
        reference operator [](difference_type n) const { ... }
        Derived& operator +=(difference_type n) { ... }
        ...
        friend difference_type operator -(IteratorFacade const& lhs,
            IteratorFacade const& rhs) { ... }
        friend bool operator <(IteratorFacade const& lhs,
            IteratorFacade const& rhs) { ... }
        ...
    };
    
    • 为了简洁省略了一些声明,然而实现列出的这些也足够麻烦。幸运的是,这些接口能提取一些核心操作
      • 对所有迭代器
        • dereference():访问迭代器值(常通过使用operator*和->)
        • increment():移动迭代器指向序列中的下个项目
        • equals():比较两个迭代器是否指向同一个项目
      • 对双向迭代器
        • decrement():移动迭代器指向列表中的前一个项目
      • 对随机访问迭代器
        • advance():往前或往后移动迭代器n步
        • measureDistance():计算两个迭代器之间的距离
    • facade的角色是适配一个只实现核心操作提供的类型,从而提供给所有迭代器接口。IteratorFacade的实现主要涉及将迭代器语句映射到最小接口,下例中使用成员函数asDerived()访问CRTP派生类
    Derived& asDerived() { return *static_cast<Derived*>(this); }
    Derived const& asDerived() const {
        return *static_cast<Derived const*>(this);
    }
    
    • 给出这个定义,大部分facade实现就是直截了当的,这里只给出一些输入迭代器需求的定义,其他类似
    reference operator*() const {
        return asDerived().dereference();
    }
    Derived& operator++() {
        asDerived().increment();
        return asDerived();
    }
    Derived operator++(int) {
        Derived result(asDerived());
        asDerived().increment();
        return result;
    }
    friend bool operator== (IteratorFacade const& lhs,
        IteratorFacade const& rhs) {
        return lhs.asDerived().equals(rhs.asDerived());
    }
    

    定义一个链表迭代器

    • 使用IteratorFacade的定义,可以轻易定义一个迭代器给一个简单的链表类
    // inherit/listnode.hpp
    template<typename T>
    class ListNode {
    public:
        T value;
        ListNode<T>* next = nullptr;
        ~ListNode() { delete next; }
    };
    
    // inherit/listnodeiterator0.hpp
    
    template<typename T>
    class ListNodeIterator
    : public IteratorFacade<ListNodeIterator<T>, T,
    std::forward_iterator_tag>
    {
        ListNode<T>* current = nullptr;
    public:
        T& dereference() const {
            return current->value;
        }
        void increment() {
            current = current->next;
        }
        bool equals(ListNodeIterator const& other) const {
            return current == other.current;
        }
        ListNodeIterator(ListNode<T>* current = nullptr) :
        current(current) { }
    };
    

    隐藏接口

    • ListNodeIterator实现上的一个缺点是,需要将操作暴露为public接口,为了隐藏接口,可以修改IteratorFacade,使其通过一个分离的访问类IteratorFacadeAccess在派生的CRTP类上执行操作
    // inherit/iteratorfacadeaccessskel.hpp
    
    // 友元该类以允许IteratorFacade访问核心的迭代器操作
    // 即friend class IteratorFacadeAccess;
    class IteratorFacadeAccess {
        // 只有IteratorFacade能使用这些定义
        template<typename Derived, typename Value,
            typename Category, typename Reference,
            typename Distance>
        friend class IteratorFacade;
    
        // 所有迭代器的要求
        template<typename Reference, typename Iterator>
        static Reference dereference(Iterator const& i) {
            return i.dereference();
        }
        ...
    
        // 双向迭代器的要求
        template<typename Iterator>
        static void decrement(Iterator& i) {
            return i.decrement();
        }
    
        // 随机访问迭代器的要求
        template<typename Iterator, typename Distance>
        static void advance(Iterator& i, Distance n) {
            return i.advance(n);
        }
        ...
    };
    

    迭代器适配器

    • IteratorFacade使得建立一个迭代器适配器很简单,适配器使用一个已有的迭代器并暴露一个新的提供底层序列一些转换视图的迭代器,比如有一个Person值容器
    // inherit/person.hpp
    struct Person {
        std::string firstName;
        std::string lastName;
        friend std::ostream& operator<<(std::ostream& strm,
            Person const& p) {
            return strm << p.lastName << ", " << p.firstName;
        }
    };
    
    • 然而不需要迭代容器中Person的所有值,只想看到firstName,这需要开发一个名为ProjectionIterator的迭代器适配器,它允许将基本迭代器的值投射到某个数据成员的指针上,如Person::firstName
    • 一个ProjectionIterator是一个迭代器,它根据基本迭代器和迭代器将暴露的值类型定义
    // inherit/projectioniteratorskel.hpp
    template<typename Iterator, typename T>
    class ProjectionIterator
    : public IteratorFacade<
        ProjectionIterator<Iterator, T>,
        T,
        typename std::iterator_traits<Iterator>::iterator_category,
        T&,
        typename std::iterator_traits<Iterator>::difference_type>
    {
        using Base = 
            typename std::iterator_traits<Iterator>::value_type;
        using Distance =
            typename std::iterator_traits<Iterator>::difference_type;
    
        Iterator iter;
        T Base::* member;
    
        friend class IteratorFacadeAccess
        ... // implement core iterator operations for IteratorFacade
    public:
        ProjectionIterator(Iterator iter, T Base::* member)
        : iter(iter), member(member) { }
    };
    
    template<typename Iterator, typename Base, typename T>
    auto project(Iterator iter, T Base::* member) {
        return ProjectionIterator<Iterator, T>(iter, member);
    }
    
    • 每个投影迭代器保存两个值,iter和member,iter是基本序列的迭代器,member是一个数据成员的指针。IteratorFacade的模板实参中,第一个是ProjectionIterator本身(以启用CRTP),第二个(T)和第四个(T&)是投影迭代器的值和引用类型,第三个和第五个只是通过基本迭代器的型别和不同类型传递。因此当Iterator是一个输入迭代器时,投影迭代器也是输入迭代器,Iterator是双向迭代器则投影迭代器也是双向迭代器,以此类推。project()函数使得建立投影迭代器十分简单
    • 唯一错过的一点是IteratorFacade核心要求的实现,最有趣的是dereference()
    T& dereference() const {
        return (*iter).*member;
    }
    
    • 其余的操作根据基本迭代器实现
    void increment() {
        ++iter;
    }
    bool equals(ProjectionIterator const& other) const {
        return iter == other.iter;
    }
    void decrement() {
        --iter;
    }
    
    • 简单起见,省略了随机访问迭代器定义,其实现类似
    • 使用投影迭代器,可以打印一个包含Person值的vector中的firstName
    // inherit/projectioniterator.cpp
    
    #include <vector>
    #include <algorithm>
    #include <iterator>
    
    int main()
    {
        std::vector<Person> authors =
            { {"David", "Vandevoorde"},
            {"Nicolai", "Josuttis"},
            {"Douglas", "Gregor"} };
        std::copy(project(authors.begin(), &Person::firstName),
            project(authors.end(), &Person::firstName),
            std::ostream_iterator<std::string>(std::cout, "\n"));
    }
    
    // output
    David
    Nicolai
    Douglas
    

    Mixins

    • 考虑一个由一系列点组成的简单的Polygon类
    class Point {
    public:
        double x, y;
        Point() : x(0.0), y(0.0) { }
        Point(double x, double y) : x(x), y(y) { }
    };
    
    class Polygon {
    private:
        std::vector<Point> points;
    public:
        ... // public operations
    };
    
    • 如果用户可以扩展与每个点关联的信息来包含特定应用的数据,如颜色,这个类将会更有用。一个选择是基于点的类型参数化Polygon
    template<typename P>
    class Polygon {
    private:
        std::vector<P> points;
    public:
        ... // public operations
    };
    
    • 用户可以使用继承来创建自己的Point-like数据类型,以提供和Point相同但包含特定应用数据的接口
    class LabeledPoint : public Point
    {
    public:
        std::string label;
        LabeledPoint() : Point(), label("") { }
        LabeledPoint(double x, double y) : Point(x, y), label("") {}
    };
    
    • 这个实现有它的缺点,它要求Point类型暴露给用户来继承,并且LabeledPoint的作者要小心提供和Point完全一样的接口,否则不能用于Polygon。如果Point由Polygon模板的一个版本变为另一个,这些限制就会引起很多问题,比如新的Point构造函数要求更新每个派生类
    • Mixins提供一个可选方法来定制一个类型的行为而不需要继承它。一个支持mixins的类模板将接受任意数量额外类
    template<typename... Mixins>
    class Point : public Mixins...
    {
    public:
        double x, y;
        Point() : Mixins()..., x(0.0), y(0.0) { }
        Point(double x, double y) : Mixins()..., x(x), y(y) { }
    };
    
    • 现在可以mix in一个包含一个label的基类来产生一个LabeledPoint
    class Label {
    public:
        std::string label;
        Label() : label("") { }
    };
    
    using LabeledPoint = Point<Label>;
    
    • 或者甚至mix in几个基类
    class Color {
    public:
        unsigned char red = 0, green = 0, blue = 0;
    };
    using MyPoint = Point<Label, Color>;
    
    • 使用这个mixin-based Point,引入额外信息给Point而不改变接口变得更简单,Polygon也更容易使用和扩展。用户只需要用Point特化到mixin类(上述的Label或Color)的隐式转换就能访问数据或接口。此外,把mixins提供给Polygon类模板本身,Point类可以完全隐藏
    template<typename... Mixins>
    class Polygon
    {
    private:
        std::vector<Point<Mixins...>> points;
    public:
        ... // public operations
    };
    

    Curious Mixins

    • mixins结合CRTP将变得更强大,这里每个mixins实际是一个类模板,它由派生类类型提供,并允许派生类的额外定制。一个CRTP-mixin版本的Point如下
    template<template<typename>... Mixins>
    class Point : public Mixins<Point>...
    {
    public:
        double x, y;
        Point() : Mixins<Point>()..., x(0.0), y(0.0) { }
        Point(double x, double y) : Mixins<Point>()..., x(x), y(y) { }
    };
    
    • 这个构建要求对每个将被mix in的类做一些额外工作,因此如Label和Color等类需要变为类模板。然而,现在mix-in的类可以调整行为以适应将要mix into的派生类的实例化。比如,可以把ObjectCounter模板mix into Point来计算由Polygon创建的点数量

    参数化虚拟性

    • 模板可以直接参数化类型、常量(nontype)和模板的实体,也能间接参数化其他属性,如成员函数的虚拟性,mixins同样也可以
    #include <iostream>
    
    class NotVirtual {
    };
    
    class Virtual {
    public:
        virtual void foo() {
        }
    };
    
    template <typename... Mixins>>
    class Base : private Mixins... {
    public:
        // the virtuality of foo() depends on its declaration
        // (if any) in the base class Mixins...
        void foo() {
           std::cout << "Base::foo()" << '\n';
        }
    };
    
    template <typename... Mixins>
    class Derived : public Base<Mixins...> {
    public:
        void foo() {
            std::cout << "Derived::foo()" << '\n';
        }
    };
    
    int main()
    {
        Base<NotVirtual>* p1 = new Derived<NotVirtual>;
        p1->foo();  // calls Base::foo()
    
        Base<Virtual>* p2 = new Derived<Virtual>;
        p2->foo();  // calls Derived::foo()
    }
    
    • 虽然这项技术可以让类模板同时用作实例化和继承,但这是一把双刃剑,一般倾向于分散功能,给类模板减负

    命名模板参数

    • 模板常常带有一长串类型参数,不过通常都设有默认值
    template<typename Policy1 = DefaultPolicy1,
        typename Policy2 = DefaultPolicy2,
        typename Policy3 = DefaultPolicy3,
        typename Policy4 = DefaultPolicy4>
    class BreadSlicer {
        ...
    };
    
    • 但如果必须指定某个缺省值的实参,还必须指定之前的所有实参,过于繁琐。为了只指定需要的实参,方法是将缺省类型值放到一个基类中,再通过派生覆盖掉某些类型值,这样通过辅助类就可以只指定一个实参,如BreadSlicer<Policy3_is<Custom> >。用辅助类做模板参数则每个辅助类都可以描述四个policy中的任意一个,因此所有模板参数的默认值都相同
    template <typename PolicySetter1 = DefaultPolicyArgs,
        typename PolicySetter2 = DefaultPolicyArgs,
        typename PolicySetter3 = DefaultPolicyArgs,
        typename PolicySetter4 = DefaultPolicyArgs>
    class BreadSlicer {
        using Policies =  PolicySelector<PolicySetter1, PolicySetter2,
            PolicySetter3, PolicySetter4>;
        // use Policies::P1, Policies::P2, ... to refer to the various policies
        ...
    };
    
    • 接着实现模板PolicySelector,这个模板的任务是用typedef将各个模板实参合并到单一类型(即下面的Discriminator)中,该类型能根据指定的类型改写后面的基类中的默认的typedef成员
    // PolicySelector<A,B,C,D> creates A,B,C,D as base classes
    // Discriminator<> allows having even the same base class more than once
    
    template<typename Base, int D>
    class Discriminator : public Base {
    };
    
    template <typename Setter1, typename Setter2,
        typename Setter3, typename Setter4>
    class PolicySelector : public Discriminator<Setter1,1>,
        public Discriminator<Setter2,2>,
        public Discriminator<Setter3,3>,
        public Discriminator<Setter4,4> {
    };
    
    • 还要把默认值集中到一个基类中
    // name default policies as P1, P2, P3, P4
    class DefaultPolicies {
    public:
        using P1 = DefaultPolicy1;
        using P2 = DefaultPolicy2;
        using P3 = DefaultPolicy3;
        using P4 = DefaultPolicy4;
    };
    
    • 为了防止多次继承产生二义性,使用虚继承
    // class to define a use of the default policy values
    // avoids ambiguities if we derive from DefaultPolicies more than once
    class DefaultPolicyArgs : virtual public DefaultPolicies {
    };
    
    • 最后用几个模板覆盖默认的policy参数
    template <typename Policy>
    class Policy1_is : virtual public DefaultPolicies {
    public:
        using P1 = Policy; // overriding type alias
    };
    
    template <typename Policy>
    class Policy2_is : virtual public DefaultPolicies {
    public:
        using P2 = Policy; // overriding type alias
    };
    
    template <typename Policy>
    class Policy3_is : virtual public DefaultPolicies {
    public:
        using P3 = Policy; // overriding type alias
    };
    
    template <typename Policy>
    class Policy4_is : virtual public DefaultPolicies {
    public:
        using P4 = Policy; // overriding type alias
    };
    
    • 现在把模板实例化为
    BreadSlicer<Policy3_is<CustomPolicy>> bc;
    
    • 这时模板BreadSlicer中的类型Polices被定义为
    PolicySelector<Policy3_is<CustomPolicy>,
        DefaultPolicyArgs,
        DefaultPolicyArgs,
        DefaultPolicyArgs>
    
    • 在模板BreadSlicer中可以用Policies::P3等限定名称引用这四个policy
    template<...>
    class BreadSlicer {
        ...
    public:
        void print () {
            Policies::P3::doPrint();
        }
        ...
    };
    
    • 下面是完整示例
    #include <iostream>
    
    // PolicySelector<A,B,C,D> creates A,B,C,D as base classes
    // - Discriminator<> allows having even the same base class more than once
    
    template <typename Base, int D>
    class Discriminator : public Base {
    };
    
    template <typename Setter1, typename Setter2,
              typename Setter3, typename Setter4>
    class PolicySelector : public Discriminator<Setter1,1>,
                           public Discriminator<Setter2,2>,
                           public Discriminator<Setter3,3>,
                           public Discriminator<Setter4,4> {
    };
    
    
    // default policies
    
    class DefaultPolicy1 {};
    class DefaultPolicy2 {};
    class DefaultPolicy3 {
      public:
        static void doPrint() {
            std::cout << "DefaultPolicy3::doPrint()\n";
        }
    };
    class DefaultPolicy4 {};
    
    
    // define default policies as P1, P2, P3, P4
    class DefaultPolicies {
      public:
        using P1 = DefaultPolicy1;
        using P2 = DefaultPolicy2;
        using P3 = DefaultPolicy3;
        using P4 = DefaultPolicy4;
    };
    
    
    // class to define a usage of the default policy values
    // - avoids ambiguities if we derive from DefaultPolicies more than once
    class DefaultPolicyArgs : virtual public DefaultPolicies {
    };
    
    
    // class templates to override the default policy values
    
    template <typename Policy>
    class Policy1_is : virtual public DefaultPolicies {
      public:
        using P1 = Policy;  // overriding type alias
    };
    
    template <typename Policy>
    class Policy2_is : virtual public DefaultPolicies {
      public:
        using P2 = Policy;  // overriding type alias
    };
    
    template <typename Policy>
    class Policy3_is : virtual public DefaultPolicies {
      public:
        using P3 = Policy;  // overriding type alias
    };
    
    template <typename Policy>
    class Policy4_is : virtual public DefaultPolicies {
      public:
        using P4 = Policy;  // overriding type alias
    };
    
    
    // create class template with four policies and default values
    
    template <typename PolicySetter1 = DefaultPolicyArgs,
              typename PolicySetter2 = DefaultPolicyArgs,
              typename PolicySetter3 = DefaultPolicyArgs,
              typename PolicySetter4 = DefaultPolicyArgs>
    class BreadSlicer {
        typedef PolicySelector<PolicySetter1, PolicySetter2,
                               PolicySetter3, PolicySetter4>
                Policies;
        // use Policies::P1, Policies::P2, //... to refer to the various policies.
      public:
        void print () {
            Policies::P3::doPrint();
        }
        //...
    };
    
    
    // define a custom policy
    class CustomPolicy {
      public:
        static void doPrint() {
            std::cout << "CustomPolicy::doPrint()\n";
        }
    };
    
    int main()
    {
        BreadSlicer<> bc1;
        bc1.print();
    
        BreadSlicer<Policy3_is<CustomPolicy>> bc2;
        bc2.print();
    }
    

    相关文章

      网友评论

        本文标题:【C++ Templates(19)】模板与继承

        本文链接:https://www.haomeiwen.com/subject/qcfaeftx.html