zoukankan      html  css  js  c++  java
  • 【effective c++读书笔记】【第7章】模板和泛型编程(3)

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

    对条款24的例子进行模板化:

    #include<iostream>
    using namespace std;
    
    template<typename T>
    class Rational{
    public:
    	Rational(const T& n = 0, const T& d = 1) :numerator(n), denominator(d){}//构造函数刻意不为explicit,为了隐式类型转换
    	const T getNumerator() const{ return numerator; }
    	const T getDenominator() const{ return denominator; }
    private:
    	T numerator;
    	T denominator;
    };
    template<typename T>
    const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs){
    	return Rational<T>(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
    }
    int main(){
    	Rational<int> oneEighth(1, 8);
    	Rational<int> oneHalf(1, 2);
    	Rational<int> result = oneHalf*oneEighth;
    	//result = oneHalf * 2;//无法通过编译
    	system("pause");
    	return 0;
    }
    

    上述例子中result = oneHalf * 2; 无法通过编译的原因在于实参推导,对oneHalf进行推导,operator*的第一个参数被声明为Rational<T>,而传递的第一个参数是Rational<int>,所以T一定是int;operator*的第一个参数被声明为Rational<T>,而传递的第一个参数是2,因为在template实参推导过程中并不会考虑采纳“通过构造函数而发生的”隐式类型转换,所以2无法转换成Rational<int>进而推导出T为int。

    利用如下事实就可以解决上述问题:template class内的friend声明式可以指涉某个特定函数。class template并不依赖template实参推导(后者只施行于function template身上),所以编译器总是能在classRational<T>具现化时得知T。

    #include<iostream>
    using namespace std;
    
    template<typename T> class Rational;
    template<typename T>
    const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs);
    
    template<typename T>
    class Rational{
    public:
    	Rational(const T& n = 0, const T& d = 1) :numerator(n), denominator(d){}
    	const T getNumerator() const{ return numerator; }
    	const T getDenominator() const{ return denominator; }
    	friend const Rational operator*(const Rational& lhs, const Rational& rhs){ return doMultiply(lhs, rhs); }
    private:
    	T numerator;
    	T denominator;
    };
    
    template<typename T>
    const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs){
    	return Rational<T>(lhs.getNumerator()*rhs.getNumerator(), lhs.getDenominator()*rhs.getDenominator());
    }
    int main(){
    	Rational<int> oneEighth(1, 8);
    	Rational<int> oneHalf(1, 2);
    	Rational<int> result = oneHalf*oneEighth;
    	result = oneHalf * 2;
    	cout << result.getNumerator() << "/" << result.getDenominator() << endl;
    
    	system("pause");
    	return 0;
    }
    

    上述例子中的技术虽然使用了friend,却与传统的friend用途“访问class的non-public成分”毫不相干。为了让类型转换可能发生与所有实参身上,我们需要一个non-member函数(条款24);为了让这个函数被自动具体化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是让它成为一个friend。

    请记住:

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

    条款47:请使用traits class表现类型信息

    1、STL共有5种迭代器:

    a、input迭代器,只能向前移动,一次一步。客户只能读取它所指的对象,且只能读取一次。它模仿指向输入文件的阅读指针(read pointer);C++程序中的istream_iterators就是这类的代表。

    b、output迭代器,只能向前移动,一次一步。客户只能写入它所指的对象,且只能写入一次。它模仿指向输出文件的涂写指针(write pointer);ostream_iterators是这一类代表。

    c、forward迭代器,继承自input迭代器,可以做上述两种迭代器能做的每一件事,而且可以读写它所指的对象一次以上。

    d、bidirectional迭代器,继承自forward迭代器,可以向前向后移动。STL中的list、set、multiset、map、和multimap迭代器就是这一类迭代器。

    e、randomaccess迭代器,继承自bidirectional迭代器。它可以在常量时间内向前或向后跳跃任意距离,类似原始指针,内置指针就可以当做random access迭代器使用。vector、deque和string的迭代器就是这类。

    2、例子:

    //deque迭代器是random access迭代器,所以应该这样:
    template<...>
    class deque{
    public:
    	class iterator{
    	public:
    		typedef random_access_iterator_tag iterator_category;
    		...
    	};
    	...
    };
    //list迭代器可双向行进,所以应该这样:
    template<...>
    class list{
    public:
    	class iterator{
    	public:
    		typedef bidirectional_iterator_tag iterator_category;
    		...
    	};
    	...
    };
    //对于iterator_traits,只要响应iterator class的嵌套式typedef即可:
    template <typename IterT>
    struct iterator_traits{
    	typedef typename IterT::iterator_category iterator_category;
    	...
    };
    //偏特化,以iterator_traits为指针指定的迭代器类型如下:
    template<typename IterT>
    struct iterator_traits<IterT*>{
    	typedef random_access_iterator_tag iterator_category;
    	...
    };
    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))
    	...
    }

    上述例子说明设计并实现traits class的步骤是:

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

    b、为该信息选择一个名称(例如iterator_category)。

    c、提供一个模板和一组特化版本(如iterator_traits),内含希望支持的类型相关信息。

    3、上述例子IterT类型在编译期间获取,所以iterator_traits<IterT>::iterator_category也可在编译期间确定。但if语句却在运行期核定。怎么办?可以使用重载的办法。

    例子:

    //这份实现用于random access迭代器
    template<typename IterT, typename DistT>
    void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag){
    	iter += d;
    }
    //这份实现用于bidirectional迭代器
    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;
    		}
    	}
    }
    //这份实现用于input迭代器
    template<typename IterT, typename DistT>
    void doAdvance(IterT& 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(IterT& iter, DistT& d){
    	doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
    }
    

    上述例子说明使用一个traits class的方法是:

    a、建立一组重载函数或函数模板,彼此间的差异只在于各自的traits参数,令每个函数实现代码与其接受的traits信息相应和。

    b、建立一个控制函数或函数模板,它调用上述重载函数并传递traits class所提供的信息。

    请记住:

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

    Effective C++ 条款 48:认识template元编程

    1、template metaprogramming(模板元编程)是编写template-based c++程序并执行于编译期的过程。是以c++写成,执行于c++编译器内的程序。

    2、模板元编程执行与C++编译期,因此可将工作从运行期转移到编译期,导致的一个结果是某些错误原本在运行期才能侦测到,现在可在编译期找出来,另一个结果是使用TMPC++程序可能在每一方面更高效:较小的可执行文件、较短的运行期、较少的内存需求。但是将工作从运行期转移到编译期的结果是编译时间能变长了

    3、模板元编程是个图灵完全机器,可以声明变量、执行循环、编写及调用函数……模板元编程并没有真正的循环构件,循环由递归(recursion)完成。TMP递归甚至不是正常的递归,因为TMP递归不涉及递归函数调用,而是涉及“递归模板具现化”(recursive template instantiation)。

    例子:

    #include<iostream>
    using namespace std;
    
    template<unsigned int n>
    class Factorial{
    public:
    	enum{ value = n*Factorial<n - 1>::value };
    };
    template<>
    class Factorial<0>{
    public:
    	enum { value = 1 };
    };
    
    int main(){
    	cout << Factorial<5>::value << endl;
    	cout << Factorial<10>::value << endl;
    
    	system("pause");
    	return 0;
    }
    

    请记住:

    • Template metaprogramming(TMP,模板元编程)可将工作由运行期移到编译期,因而得以实现早期错误侦测和更高的执行效率。
    • TMP可被用来生成“基于政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码。

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    C++ 虚函数 、纯虚函数、接口的实用方法和意义
    C++ static成员变量与static成员函数
    C++格式化输出的好东西
    const的用法,特别是用在函数前面与后面的区别!
    SAP CRM WebClient UI cross component跳转的一个具体例子
    如何用ABAP代码读取SAP Business partner的附件数据
    SAP CRM Enterprise Search initial load遇到错误该如何处理
    使用SAP WebIDE给SAP UI5应用添加data source
    使用Selenium自动化测试SAP CRM WebClient UI
    php 对象数组互转
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785433.html
Copyright © 2011-2022 走看看