Effective C++ (7): Templates and Generic Programming

Introduction

这一章主要介绍了模板编程, 最核心的思想就是尽可能地把运行期所做的事情移动至编译期完成. 最后还简要介绍了以下 TMP

Rule 41: Understand implicit interfaces and compile-time polymorphism

了解隐式编程和编译期多态.
对于着一条 Rule, 需要了解到的就是 template 的多态与 classes 的多态有所不同, 一个是发生与编译期, 一个是发生于运行期. 就效率而言 template 更快. 但是 template 要保持接口的一致是隐式的(implicit), 就是说如果在写一个 template 函数中, 如果使用了 w.size() 这样的函数, 实际上也就隐式要求了这个 type 需要有size这个接口函数. 所以也增加了写代码的难度.

Remeber:

  • classes 和 templates 都支持接口(interfaces) 和 多态(polymorphism)
  • 对 classes 而言接口是显式的(explicit), 以函数签名为中心. 多态则是通过 virtual 函数发生在运行期
  • 对 template 参数而言, 接口是隐式(implicit)的, 基于有效表达式, 多态则是通过 template 具体化和函数重载解析发生于编译期

Rule 42: Understand the two meanings of typename

看一端代码就好:

template<typename/class C>  //template 和 class 是一样的
void print2nd(const C& container)
{
    typename C::const_iterator iter(container.begin()); //对于嵌套从属名称, 需要在前面加上typename
}

Remeber:

  • 声明 template 参数的时候, class = typename
  • 使用关键字 typename 表示嵌套从属类型名称, 但是不能在 base class lists(基类列) 或 member initialization list(成员初值列) 内以它作为 base class 修饰符

Rule 43: Know how to access names in templatized base classes

当继承 base class templates 时候, 需要通过 ‘this->’ 来调用 base class templates 中的成员. 例如:

template<typename Company>
class MsgSender{
public:
    ...
    void sendClear(const MsgInfo& info)
    {
        std::string msg;
        Company c;
        c.sendCleartext(msg);
    }
    void sendSecret(const MsgInfo& info)
    { ... }
};

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>
{
public:
    void sendClearMsg(const MsgInfo &info)
    {
        ...
        sendClear(info);  // 无法通过编译
        this->sendClear(info);  // 通过编译
        ...
    }
};

这是因为编译器无法确定其中的Company是否有 sendClear 这个函数(考虑到特化的问题). 所以需要通过加上 “this->” 来向编译器承诺 base class template 的任何特化版本都将支持其一般(泛化)版本所提供的接口

除了加上 “this->” 之外还有另外两种方法:

  1. 使用 using 声明式, 例如 using MsgSender::sendClear
  2. 使用明确资格修饰符, 例如 MsgSender::sendClear(info). 不建议使用这种方法, 因为会关闭 ” virtual 绑定行为 “

Remeber:
当继承 template class 的时候, 需要通过 “this->” 来调用 base class templates 内的成员名称, 或者通过写出 “base class 资格修饰符” 完成

Rule 44: Factor parameter-independent code out of templates

使用template可能会导致代码膨胀的问题. 特别要注意对于 非类型参数(non-type parameter), 例如:

template<typename T, std::size_t n>
class SquareMatrix{
public:
    ...
    void invert();
};

上面的 size_t n 就是非类型参数, 当 n 取 5, 10 的时候, 就会产生两份代码, 除了常量5和10不同, 其他都相同, 这样就容易造成代码膨胀.
解决这种问题最好的方法就是, 将操纵的代码放在一个 base template class 中, 如果存在数据的问题就在 base template class 中保存一个指向数据的指针, 然后真正的数据放在 derived template class 中, derived class 接受非类型参数, derived template class 中除了存放数据, 其他的函数就直接调用 base 中的函数, 这样一来重复的代码只会有 derived class 中的代码.
例如:

template<typename T>        // 减少代码膨胀
class SquareMatrixBase{
protected:
    SquareMatrixBase(std::size_t n, T* pMem):
        size(n), pData(pMem) {}
    void setDataPtr(T* ptr) { pData = ptr; }
    void invert(std::size_t matrixSize);
    ...
private:
    std::size_t size;
    T* pData;   // 存储数据指针
};

template<typename T, std::size_t n>
class SquareMatrix: private SquareMatrixBase<T>{
public:
    SquareMatrix() : SquareMatrixBase<T>(n, data) {}
    ...
private:
    T data[n*n];
};

除了这种由 non-type template parameters 之外, type template parameters 也会引起膨胀. 例如含有指针的时候 list<const int*>list<const double*>, 最好使用 void* 来代替.

Remeber:

  • Templates会根据参数生成多份代码, 容易造成代码冗余
  • 因非类型模板参数(non-type template parameters)而造成的代码膨胀, 可通过 以函数参数或者 class成员变量替换template参数.
  • 因类型参数(type parameters)而造成的代码膨胀, 可以让带有完全相同二进制表述(binary representation)的表现类型共享实现代码

Rule 45: Use member function templates to accept “all compatible types”

运用成员函数模板接收所有兼容类型

举个例子, 希望能够实现:

template<class T>
class shared_ptr{
public:
    template<class Y>
        explicit shared_ptr(Y* p);  // 只可以显示转换
    template<class Y>
        shared_ptr(shared_ptr<Y> const& r); // 允许隐式转换
    ...
};

这个就是在模板类中定义泛化模板函数.

Remeber:

  • 请使用 member function templates(成员函数模板) 生成 “可接收所有兼容类型” 的函数
  • 如果你声明了 member templates 用于”泛化 copy 构造” 或 “泛化 assignment 操作”, 你还是需要声明正常的 copy 构造函数和 copy assignment 操作符

Rule 46: Define non-member functions inside templates when type conversions are desired

之前有提到过, 如果对于 “所有参数之类型转换” 的函数, 就把函数作为 non-member function, 再提供隐式类型转换.
不过对于 template class 却有不同. 需要将函数定义为 friend 函数, 并且就在类内部实现

template<typename T>
class Ration{
public:
    ...
    friend const Rational operator*(const Rational& lhs, const Rational& rhs)    // 声明为 friend 函数
    {
        return Rational(lhs.numerator() * rhs.numerator(),
                    lhs.denominator() * rhs.denominator());
    }   // 在类内部实现
};

Remeber:
当编写一个 class template 的时候, 他所提供的函数支持 “所有参数之隐式类型转换” 时, 请将那些函数定义为 “class template 内部的friend函数”

Rule 47: Use traits classes for information and types

首先, 以 iterator 的5种不同类型作为引入:

  1. input_iterator: 单向, 每次只能操作一次
  2. output_iterator: 同上
  3. forward_iterator: 可以做之前两种迭代器的任意一种, 并且可以操作多次
  4. bidirectional_iterator: 可以双向操作
  5. random_access_iteratror: 最强大, 以常量时间随意跳跃

好的, 下面介绍 Traints classes + 函数重载来解决下面这个问题: 我们需要写一个让 iterator 进行跳转的函数, 根据不同的 iterator 类型有不同的实现:

template<typename IterT, typename DistT>
void advance(IterT &iter, DistT d);

那么我们要解决两个问题: 1. 如何判断 IterT 是哪种类型的 iterator. 2. 如果做到在编译器根据类型的不同有不同的实现

对于这种情况, 我们采用了 Traits classes 来让 iterator 夹带类型信息. 简而言之, 就是定义一个 iterator_traits, 并用它来传递类型信息, 并且在各个 iterator 自身定义 tag 信息, 而 iterator_traits 就是把这个 tag 信息传递出去

template<typename IterT>
struct iterator_traits{     // IterT 的 iterator_category 其实就是用来表现 "IterT自己来说自己是啥", 所以说 iterator_traits 有个传递的作用
    typedef typename IterT::iterator_category iterator_category;  // typedef typename 用来告诉编译器是个别名, 不是类型
    ...
};

/* 对于指针迭代器提供一个偏特化版本 */
template<typename IterT>
struct iterator_traits<IterT*>{ 
    typedef random_access_iterator_tag iterator_category;
    ...
};

template <...>
class deque{
public:
    class iterator{
    public:
        typedef random_access_iterator_tag iterator_category; // 定义自己的类型, random_access_iterator_tag 是一个 struct
        ...
    };
    ...
};

/* 通过建立一个重载函数 doAdvance 接受traits信息,  和一个控制函数 advance来传递traits信息 */
template<typename IterT, typaname DistT>
void advance(IterT &iter, DistT d)
{
    doAdvance(iter, d, 
        typename std::iterator_traits<IterT>::iterator_category());
}

template<typename IterT, typaname DistT>
void doAdvance(IterT &iter, DistT d,
                    std::random_access_iterator_tag)
{
    iter += d; 
}

template<typename IterT, typaname DistT>
void doAdvance(IterT &iter, DistT d,
                    std::bidirectional_iterator_tag)
{
    if ( d >= 0) { while (d--) ++iter; }
    else { while(d++) --iter; }
}

Remeber:

  • Traits classes 使得”类型相关信息”在编译期可用. 他们以 templates 和 “templates 特化” 完成实现
  • 整合重载技术(overloading)后, traints classes 有可能在编译期对类型执行 if…else 测试

Rule 48: Be aware of template metaprogramming

模板元编程(TMP) 被证明是图灵完备的, 之前 Rule 47就是TMP的一个案例, 由于TMP还是比较麻烦并且不直观的, 所以这里简要介绍一个 TMP 的 Hello world, 求 factorial:

template<unsigned n>
struct Factorial{
    enum { value = n * Factorial<n-1>::value };
};

template<>          //特化版本
struct Factorial<0>{
    enum { value = 1 };
};

int main(){
    std::cout<<< Factorial<10>::value ;
}

Remeber:
Template metaprogramming(TMP, 模板元编程) 可将工作由运行期移往编译期, 因而得以实现早期错误侦测和更高的执行效率

系列文章

Effective C++ (1): Accustoming Yourself to C++
Effective C++ (2): Constructors, Destructors, and Assignment Operators
Effective C++ (3): Resource Management
Effective C++ (4): Designs and Declaration
Effective C++ (5): Implementation
Effective C++ (6): Inheritance and Oject-Oritent Design
Effective C++ (7): Templates and Generic Programming
Effective C++ (8): Customizing new and delete
Effective C++ (9): Miscellany

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值