Effective C++(条款45-47)

条款45、运用成员函数模板接受所有兼容类型

所谓智能指针是“行为像指针”的对象,并提供指针没有的机能。

真实指针做的很好的一件事情就是,支持隐式转换。继承类指针可以隐式转换为基类指针,指向non-const的对象可以转换为指向const的对象等等。

但是,同一个template的不同具现体之间并不存在什么与生俱来的固有关系,比如说,带有derived-base关系的B,D两类型分别是具现化某个template,产生出来的两个具现体并不带有derived-base关系。


就原理而言,因为一个template可以被无限量具现化,以致生成无限量函数,因此我们似乎不能为一个模板写一个构造函数,而要为它写一个构造模板。这样的模板就是所谓的member function template(常简称为member template),其作用就是为class生成函数。


1、使用member function template(成员函数模板)生成“可接受所有兼容类型”的函数

template<typename T>
class SmartPtr{
public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other);//member template,为了生成泛化copy构造函数
    ...
};

以上代码的意思是,对于任何类型T和任何类型U,这里可以根据SmartPtr<U>生成一个SmartPtr<T>,U和T的类型是同一个template的不同具现化,有时候我们称为泛化copy构造函数。


我们可以在构造模板实现代码中约束转换行为,使它为我们提供更多的功能:

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;
    ...
};


member function template的效用不限于构造函数,他们常扮演的另一个角色是支持赋值操作

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);//构造,来自任何兼容的shared_ptr
    template<class Y>
    explicit shared_ptr(weak_ptr<Y> const& r);//构造,来自任何兼容的weak_ptr
    template<class Y>
    explicit shared_ptr(auto_ptr<Y>& r);//构造,来自任何兼容的auto_ptr
    template<class Y>
    shared_ptr& operator=(shared_ptr<Y>const& r);//赋值,来自任何兼容的shared_ptr
     template<class Y>
    shared_ptr& operator=(auto_ptr<Y>& r);//赋值,来自任何兼容的auto_ptr
    ...
};

以上所有构造函数中,只有shared_ptr(shared_ptr<Y> const& r)没有explicit,说明从某个shared_ptr隐式转换至另一个shared_ptr类型是被允许的,但其他不被允许进行隐式转换。另一点是,trl::shared_ptr构造函数和赋值操作符的trl::auto_ptr并未被声明为const传递,那是因为这两个行为中指针被改动了。



2、如果你声明member template用于“泛化copy构造函数”或“泛化assignment操作”,你还需要声明正常的copy构造函数和assignment操作符。

因为member function template并不改变语言规则。在类内声明“泛化copy构造函数”或“泛化assignment操作”并不会阻止编译器生成他们自的的copy构造函数和assignment操作符





条款46、需要类型转换时请为模板定义非成员函数

问题现象:模板化的template内的某些东西似乎和non-template版本不同,和条款24相同的代码,但是本条款将Rational和operator*模板化了。

在条款24中,我们讨论过只有non-member函数才有能力“在所有实参身上实施隐式类型转换”,编译器知道我们尝试调用什么函数。在本条款的模板化中,在这里编译器不知道我们想要调用哪个函数。

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);
Rational<int> result=onehalf*2;//错误!无法通过编译


原因:在template实参推导过程中从不将隐式类型转换函数纳入考虑。


解决方法:为了让类型转换可能发生在所有实参身上,我们需要一个non-member函数(条款24);为了令这个函数被自动具现化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是令它成为一个friend函数;但是仅仅这样代码只能通过编译却无法连接,为了连接器能找到这个函数,我们就需要将这个函数定义在class内,让声明式与定义是合并。

template<typename T>
class Rational{
public:
    ... 
    friend const Rational operator*(const Rational& lhs,const Rational& rhs)
    {
        return Rational(lhs.numerator()*rhs.numerator(),lhs.denomerator()*rhs.denomerator());
    }
};
/*在一个class template内,template名称可被用来作为“template和其参数”的简略表达方式,所以在rational<T>内我们可以只写Rational而不必写Rational<T>,在参数名称很长的时候,这可以节省时间,让代码比较干净,如果他被声明如下,一样有效

const Rational<T> operator*(const Rational<T>&  lhs,const Rational<T>& rhs){...}*/



定义于class内部的函数都暗自成为inline,包括像operator*这样的friend函数。如果你想将复杂函数所带来的inline冲击最小化,做法是让它不做任何事情,只调用一个定义于class外部的辅助函数。

template<typename T> class Rational;
template<typename T>
const Rational<T> deMultiply(const Rational<T>& lhs,const Rational<T>& rhs)
{
    return  Rational<T>(lhs.numerator()*rhs.numerator(),lhs.denomerator()*rhs.denomerator());
}
template<typename T>
class Rational{
public:
    ... 
    friend const Rational operator*(const Rational& lhs,const Rational& rhs)
    {
        return  doMultiply(ihs,rhs);
    }
};

作为一个template,doMultiply不支持混合式乘法,但它也不需要,它在被operator*调用,而operator*支持混合式乘法操作!本质上operator*支持了类型转换所需要的任何东西,确保两个Rational对象能够相乘,然后将这两个对象传给一个适当的doMultiply template具现化,完成实际的乘法操作,协作成功。


总结:当我们编写一个class template,而她所提供的“与此template相关的”函数支持“所有参数的隐式类型转换”时,请将那些函数定义为“class template内部的friend函数"






条款47、请使用traits classes表现类型信息

STL主要由“用以表现容器、迭代器和算法”的template构成,但是也覆盖若干工具性的templates,其中一个名为advance ,用来将某个迭代器移动某个给定的距离

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

观念上advance只做iter+=d动作,但是实际上只哟random access(随机访问)迭代器才支持+=操作。首先重温下 STL迭代器的分类

input迭代器:只能向前移动,一次一步,客户只可以读取而不能改写他们所指的东西,而且只能读一次。C++程序库中istream_iterators是这一类的代表。

output迭代器:只能向前移动,一次一步,客户只能改写他们所指的东西,而且只能写一次。C++程序库中ostream_iterators是这一类的代表。

forward迭代器:这种迭代器可以做前述两种迭代器所做的每一件事,而且可以读或写其所指物一次以上,他可施行于多次性 操作算法。

bidirectional迭代器:它可以向前或向后移动,STL的list迭代器就属于这一分类,set,multiset,map,multimap也都是这一分类。

random access迭代器:是最有威力的迭代器,他可以在在常量时间内向前或向后跳跃任意距离,这种算术类似于指针算术,所以内置指针也可以被当做random access迭代器使用。vector,deque,string提供的迭代器都是这一分类的。



我们希望能够根据不同类型的迭代器提供不同的算术运算,来提高代码的效率。这样就需要首先判断迭代器的分类,获得迭代器的类型信息那就是traits让你得以进行的事:他们允许你在编译期间获得某些类型信息

traits并不是C++关键字或一个预先定义好的构件,它们是一种技术,也是一个C++程序员共同遵守的协议。这个技术的要求之一是:它对内置类型和用户自定义类型表现必须一样好

“traits 能够施行于内置类型”意味着“类型内的嵌套信息”这种东西出局,类型的traits信息必须位于类型自身之外。标准技术将它放进一个template及其一或多个特化版本中,这样的template在标准程序库中有若干个,其中针对迭代器者被命名为iterator_traits。

template<typename IterT>
struct iterator_traits;//template用来处理迭代器分类的相关信息,习惯上traits总是被实现为struct,但是往往被称为traits classes


iterator_traits的运作方式是,针对每一个类型iterT,在struct iterator_traits<IterT>内声明某个typedef名为iterator_category。这个typedef用来确认IterT的迭代器分类。

如下:

template<typename IterT>
class deque{
public:
    class iterator{
    public:
        typedef random_access_iterator_tag iterator_category;
        ...
    };
    ...
};
template<typename IterT>
class list{
public:
    class iterator{
    public:
        typedef bidirectional_iterator_tag iterator_category;
        ...
    };
    ...
};//首先每一个用户自定义的迭代器类型必须嵌套一个typedef名为iterator_category,用来确认适当的卷标结构(tag struct)
template<typename IterT>
struct iterator_traits{
    typedef typename IterT::iterator_category iterator_category;
    ...
};//iterator_traits响应iterator class的嵌套式typedef
//类型IterT的iterator_category其实是用来表现”IterT说它自己是什么"



这对用户自定义类型行得通,但对指针(也是一种迭代器)行不通,因为指针不可能嵌套typedef。为了支持指针迭代器,iterator_traits特别针对指针类型挺一个偏特化版本。由于指针的行径与random access迭代器类似,所以iterator_traits为指针指定的迭代器类型是:

template<typename IterT>
struct iteratpr_traits<IterT*>//template偏特化针对内置指针
{
    typedef random_access_iterator_tag iterator_category;
    ...
};


总结:如何设计并实现一个traits class:

1.确认若干你希望将来可取得的类型相关信息。例如对迭代器而言,我们希望将来可取得其分类。

2.为该信息选择一个名称(例如iterator_category)

3.提供一个template和一组特化版本(例如iterator_traits),内含你希望支持的类型相关信息

traits class使得“类型相关信息”在编译期可用。它们以templates和“template特化”完成实现


好,现在有了iterator_traits,我们可以对advance实践先前的伪码:

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
    if(typeid(typename std::iterator_traits<IterT>::iterator_category)==typeid(std::random_access_iterator_tag))
    ...
}

虽然这看起来正确,但是并非是我们想要的。首先他会导致编译问题。IterT类型在编译期间获知,但 if语句确是在运行期才会核定。这不仅浪费时间,也会造成可执行文件膨胀。

整合重载技术后,traits class有可能在编译期对类型执行if else测试。为了让advance的行为如我们所愿,我们需要做的是产生重载版本,内含advance的本质内容,但各自接受不同类型的iterator_category对象,我们将这个函数取名为doAdvance:

template<typename IterT,typename DistT)
void doAdvance(IterT& iter,DistT d,std::random_access_iterator_tag)
{
    iter+=d;
}
template<typename IterT,typename DistT)
void doAdvance(IterT& iter,DistT d,std::bidirectional_iterator_tag)
{
    if(d>=0){while(d--)  ++iter;}
    else{while(d++) --iter;}
}
template<typename IterT,typename DistT)
void doAdvance(IterT& iter,DistT d,std::input_iterator_tag)
{
    if(d<0){
        throw std::out_od_range("Negative distance");
    while(d--) ++iter;
}

template<typename IterT,typename DistT>
void advance(IterT& iter,DistT d)
{
    doAdvance(iter,d,typename std::iterator_traits<IterT>::iterator_category());
}//调用的doAdvance版本对iter的迭代器分类而言必须是适当的

现在我们可以总结如何使用一个traits class 了:

  • 建立一组重载函数(身份像劳工)或函数模板(例如doAdcance),彼此间的差异只在于各自的traits参数。令每个函数实现码与其接受的traits信息相应和
  • 建立一个控制函数(身份像工头)或函数模板(例如advance),它调用上述哪些“劳工函数”并传递给traits class所提供的信息

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值