effective c++读书笔记(七)

模版与泛型编程

c++template机制自身是一部完整的图灵机:它可以被用来计算任何可计算的值。

了解隐式接口和编译期多态

面向对象编程世界总是以显示接口和运行期多态解决问题。
template及泛型编程的世界,与面向对象有根本上的不同,在此世界中显示接口和运行期多态仍然存在,但重要性降低,反倒是隐式接口和编译器多态移到前头啦。
通常显示接口由函数的签名式构成。

class Widget{
public:
    Widget();
    virtual ~Widget();
    virtual std::size_t size() const;
    virtual void normalize();
    void swap(Widget& other);
};

它的public接口由一个构造函数,一个析构函数,函数及其参数类型,返回类型,常量性构成,也包括编译器产生的copy构造函数和copy assignment操作符。

隐式接口就完全不同了,他并不是基于函数签名式,而是由有效表达式组成

template<typename T>
void doProcessing(T& w){
    if(w.size() > 10 && w != someNastyWidget){
        //...
    }
}
  1. 它必须提供一个名为size的成员函数,该函数返回一个整数值
  2. 它必须支持一个operator!= 函数,用来比较两个T对象。
  3. ….
    多态这是通过template具现化和函数重载解析发生于编译期来实现的。

了解typename的双重意义

template<class T> class Widget;  //使用“class”
template<typename T> class Widget;  //使用“typename”

上述两种写法完全一样,没有不同的意义。然而c++并不总是把class和typename视为等价。有时候一定得使用typename。

template<typename C>
void print2nd(const C& container){
    if(container.size() >= 2){
        C::const_iterator iter(container.begin());  //false的用法
        ++iter;
        int value = *iter;
        std::cout<<value;
    }
}

template内出现的名称如果相依于某个template参数,称为从属名称,如果从属名称在class内呈嵌套状,称为嵌套从属名称,例如:C::const_iterator就是一个嵌套从属类型名称
不依赖于任何template参数的名称,称为非从属名称(non-dependent names)。
如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,任何时候当你想要在template中指涉一个嵌套从属类型名称,就必须在临近它的前一个位置放上关键字typename

template<typename C>
void print2nd(const C& container){
    if(container.size() >= 2){
        typename C::const_iterator iter(container.begin());  //false的用法
        //...
    }
}

typename只被用来验明嵌套从属类型名称,其他名称不该有它存在

template<typename c>
void f(const C& container, typename C::iterator iter);

“typename必须作为嵌套从属类型名称的前缀词”这一规则有一个例外是,typename不可以出现在base class list内的嵌套从属类型名称前,也不可以出现这membr initialization list中作为base class修饰符

template<typename T>
class Derived: public Base<T>::Nested{
public:
    explicit Derived(int x): Base<T>::Nested(x){
        typename Base<T>::Nested temp;
    }
};

可以使用typedef来简化名称长度

template<typename IterT>
void workWithIterator(IterT iter){
    //typename std::iterator_traits<IterT>::value_type value_type temp(*iter);
    typedef typename std::iterator_traits<IterT>::value_type value_type;
    value_type temp(*iter);
}

学习处理模板化基类内的名称

class CompanyA{
public:
    void sendCleartext(const std::string& msg);
    void sendEncrypted(cosnt std::string& msg);
    //...
};
class CompanyB{
public:
    void sendCleartext(const std::string& msg);
    void sendEncrypted(cosnt std::string& msg);
    //...
};
class MsgInfo{ //...};
template<typename Company>
class MsgSender{
public:
    void sendClear(const MsgInfo& info){
        std::string msg;
        //这儿,根据info产生信息
        Company c;
        c.sendCleartext(msg);
    }
    void sendSecret(const MsgInfo& info){  //同上
        //...
    }
};

//假设需要在每次送出信息时标记某些信息
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info){
        //将传送前的信息写到log
        sendClear(info);  //调用base class函数,但是无法通过编译
        //将传送后的信息写到log
    }
}

上述的类,在编译器是无法通过编译,因为class template定义式时,并不知道它继承什么样的class。因为Company是template参数,不到被具现化,无法确切知道它是什么。因此,就没法知道class MsgSender<Company>看起来像什么–更明确的说是不知道它是否有个sendClear函数。

//全特化类,因为companyz不需要sendClear函数
template<>
class MsgSender<CompanyZ>{
public:
    void sendSecret(const MsgInfo& info);
}
template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info){
        //将传送前的信息写到log
        sendClear(info);  //如果Company = CompanyZ,这个函数不存在
        //将传送后的信息写到log
    }
}
//因为companyz没有提供sendclear函数

为了解决上述问题(令c++不进入templatized base class观测的行为失效),有三种解决方法:
1.在base class函数调用动作之前加上this->

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info){
        //将传送前的信息写到log
        this->sendClear(info);  //假设sendClear将被继承
        //将传送后的信息写到log
    }
}

2.使用using声明式

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    using MsgSender<Company>::sendClear;
    void sendClearMsg(const MsgInfo& info){
        //将传送前的信息写到log
        sendClear(info);  //假设sendClear将被继承下来
        //将传送后的信息写到log
    }
}

虽然using在这里和条款33都可有效运作,但是解决的问题不同。33里面using为了解决derived class名称遮蔽问题。这里是编译器不进入base class作用域内查找,于是通过using告诉它,请它这么做
3. 明白指出被调用的函数位于base class内

template<typename Company>
class LoggingMsgSender: public MsgSender<Company>{
public:
    void sendClearMsg(const MsgInfo& info){
        //将传送前的信息写到log
        MsgSender<Company>::sendClear(info);  //假设sendClear被继承下来
        //将传送后的信息写到log
    }
}

缺点:如果被调用的是virtual函数,上述明确的资格修饰将会关闭virtual绑定行为
以上的三种解法,都是对编译器承诺base class template 的任何特化版本都将支持其一般化版本所提供的接口。

将与参数无法的代码抽离template

使用template可能会导致代码膨胀:其二进制码带着重复的代码、数据。其结果有可能源码看起来合身而整齐,但目标码却不是那回事。
通过共性与变性分析,使其概念十分平民化。

template<typename T, std::size_t n>
class SquareMatrix{
public:
    void invert();  //矩阵求逆
};

SquareMatrix<double, 5> sm1;
sm1.invert();
SquareMatrix<double, 10> sm2;
sm2.invert();
//导致template代码膨胀的一个例子

为了解决代码膨胀的问题:

tempalte<typename T>
class SquareMatrixBase{
protected:
    void invert(std::size_t matrixSize);
};
template<typename T, std::size_t n>
class SquareMatrx: private SquareMatrixBase{
private:
    using SquareMatrixBase<T>::invert;  //避免遮蔽base版本的invert
public:
    void invert(){ this->invert(n);}
};

但是上述的实现方法,SquareMatrixBase::invert不知道操作什么数据,虽然它从参数中知道矩阵的尺寸,但它如何知道哪个特定矩阵的数据在哪儿?只有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; }
private:
    std::size_t size;
    T* pData;  //指针,指向矩阵内容
};
//静态分配内存的方法
template<typename T, std::size_t n>
class SquareMatirx: private SquareMatrixBase<T>{
public:
    SquareMatrix(): SquareMatrixBase<T>(n,data){}
private:
    T data[n*n];
};
//动态分配内存的方法
template<typename T, std::size_t n>
class SquareMatirx: private SquareMatrixBase<T>{
public:
    SquareMatrix(): SquareMatrixBase<T>(n,0), pData(new T[n*n]) { this->setDataPtr(pData.get()); }
private:
    boost::scoped_array<T> pData;
};

以上讨论的是非类型模版参数导致的代码膨胀,其实typename parameters也会导致膨胀。例如,在某些平台上vector和vector成员函数有可能完全相同。如果你实现某些成员函数而它们操作强类型指针,你应该令它们调用另一个操作无类型指针的函数,由后者完成实际工作。

运用成员函数模版接受所有兼容类型

真实指针做的很好的一件事是,支持隐式转换。但是template的具现体之间不存在什么与生俱来的固有关系,意思是:以带有base-derived关系的B,D两类型分别具现化某个template,产生出来的两个具现体并不带有base-derived关系。因此为了达到隐式转换的目的,必须明确的编写出来。
template和泛型编程
为了实现一个智能指针,我们可以通过member function template 的方法来实现:

template<typename T>
class SmartPtr{
public:
    template<typename U>   //泛化copy构造函数
    SmartPtr(const SmartPtr<U>& other);
    //....
};
//对于任何类型T和任何类型U,可以根据SmartPtr<U>生产一个SmartPtr<T>。

但是上述的实现,可能会导致SmartPtr转换为SmartPtr情况的出现。这样就导致了错误,因为必须对member template所创建的成员函数进行筛除。

template<typename T>
class SmartPtr{
public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other): heldPtr(other.get()){  //....}
    T* get() const { return heldPtr;}
private:
    T* heldPtr;
};
//只有当存在某个隐式转换可将一个U*指针转为一个T*指针时,才通过编译。

member functiontemplate的效用除了上面的构造函数,还扮演着另一个角色是支持赋值操作。并且他不改变语言基本规则。如果程序需要一个copy构造函数,没有声明它,编译器会给你声明一个。如果想要控制copy构造的方方面面,必须同时声明泛化copy构造函数和正常的拷贝构造函数。

template<class T>
class shared_ptr{
public:
    shared_ptrshared_ptr const& r);
    template<class Y>
    shared_ptr<shared_ptr<Y> const& r);
    shared_ptr& operator=(shared_ptr const& r);
    tempalte<class Y>
    shared_ptr& operator=(shared_ptr<Y> const& r);
};

需要类型转换时请为模版定义非成员函数

template<typename T>
class Rational{
public:
    Rational(const T& numerator = 0, const T& denominator = 1);
    const T numerator() const;
    const T denominator() const;
};
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{
    //...
}

Rational<int> onehalf(1,2); //true;
Rational<int> result = onehalf * 2; //false 无法通过编译

模板化的Rational和non-template版本不同。因为template的实参推导过程中不将隐式类型转换函数纳入考虑,因为上述调用中,无法推导出2的类型。因此编译不通过。为了解决template实参推导的问题,可以将函数声明为friend声明式。

template<typename T>
class Rational{
public:
    friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};
template<typename T>
const Ration<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
    //..
}
//上述函数可以编译成功,因为firend函数可以使用隐式转换函数,使得混合式调用成功。
//但是无法通过链接,找不到定义,因为他只被声明于Rational内。没有定义出来
//解决无法链接的问题
template<typename T>
class Rational{
public:
    friend const Rational operator*(const Rational& lhs, const Rational& rhs){
        return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator());
    }
};

另一种方法,利用friend函数调用辅助函数。

template<typename T> class Rational;
template<typename T>
const Rational<T> doMultiple(const Rational<T>& lhs, const Rational<T>& rhs){
    return Rational<T>(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator());
}
template<typename T>
class Rational{
public:
    friendconst Rational operator*(const Rational& lhs, const Rational& rhs){
        return doMultiple(lhs,rhs);
    }
};

请使用traits class表现类型信息

traits是一种技术,他对内置类型和用户自定义类型的表现必须一样好。
1.确认若干你希望将来可取的的类型相关信息。例如对迭代器而言,希望将来可取的其分类
2.为该信息选择一个名称
3.提供一个template和一组特化版本,内含你希望支持的类型相关信息。
4.建立一组重载函数或函数模版,彼此之间的差异只在于各自的traits参数。
5.建立一个控制函数或函数模版,他调用上述那些函数并传递traits class所提供的信息。

struct input_iterator_tag{};
struct output_iterator_tag{};
struct forward_iterator_tag : public input_iterator_tag{};
struct bidirectional_iterator_tag: public forward_iterator_tag{};
struct random_access_iterator_tag: public bidirectional_iterator_tag{};

//advance实现方式,使用traits技术
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d){
    if(iter is a random access iterator){
        iter += d;
    }
    else{
        if(d >= 0) {while(d--) ++iter;}
        else{ while(d++) --iter;}
    }
}
template<typename IterT, typename DistT>
void doAdvance(IterI& iter, DistT d, std::random_access_iterator_tag){
    iter += d;
}
template<typename IterT, typename DistT>
void doAdvance(IterI& iter, DistT d, std::bidirectional_iterator_tag){
    if(d >= 0) {while(d--) ++iter;}
    else{ while(d++) --iter;}
}
template<typename IterT, typename DistT>
void doAdvance(IterI& iter, DistT d, std::input_iterator_tag){
    if(d < 0) {  throw std::out_of_range("Negative distance");}
    while(d--) ++iter;
}
template<typename IterT, typename DistT>
void advance(IterI& iter, DistT d){
    doAdvance(iter,d,typename std::iterator_traits<IterT>::iterator_category());
}

//traits class的定义
template<typename IterT>
struct iterator_traits<IterT*>{
    typedef random_access_iterator_tag iterator_category;
    /...
}

认识template元编程

所谓template元编程是以c++写成,执行于C++编译器内的程序。优点:1,它让某些事情更容易;2,由于template 元编程执行与C++编译器,因此可将工作从运行期移到编译期。
template元编程没有真正的循环构建,主要是由递归完成的。采用的不是递归函数调用,而是递归模版具现化。

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

template元编程值得学习的例子:1. 确保度量单位正确;2.优化矩阵计算(可以消除临时对象并合并循环);3.可以生产客户定制的设计模型实现品。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值