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

    条款44:将与参数无关的代码抽离templates

    template是节省时间和避免重复代码的一个奇妙方法。class template的成员函数只有在被使用时才被暗中具现化。但是如果不小心,使用templates可能导致代码膨胀(code bloat):其二进制代码带着重复(或几乎重复)的代码、数据、或两者。其结果可能源码看起来合身整齐,但目标码却不是那么回事。

    假设你想为固定尺寸的正方矩阵编写一个template,并且该矩阵支持求逆矩阵计算:

    #include<iostream>
    using namespace std;
    
    template<typename T, size_t n>//template支持n * n矩阵,元素类型是T
    class SquareMatrix{           //size_t我们称之为非类型参数
    public:
    	void invert(){   //求逆矩阵
    		cout << "求逆矩阵" << endl;
    	}
    };
    
    int main(){
    	SquareMatrix<double, 5> sm1;
    	sm1.invert();//调用SquareMatrix<double, 5>::invert
    
    	SquareMatrix<double, 10> sm2;
    	sm2.invert();//调用SquareMatrix<double, 10>::invert
    
    	system("pause");
    	return 0;
    }
    

    上述例子具现化两份invert。其中一个操作5*5矩阵,另一个操作10*10矩阵,但除了常量5和10,invert函数的其他部分完全相同。这是template引出代码膨胀的一个典型例子。首先修改如下:把矩阵的大小变成invert函数的参数。

    #include<iostream>
    using namespace std;
    
    template<typename T>//于尺寸无关的base class,用于正方矩阵
    class SquareMatrixBase{
    protected:
    	void invert(size_t matrixSize){
    		cout << "求逆矩阵" << endl;
    	}
    };
    template<typename T, size_t n>
    class SquareMatrix : private SquareMatrixBase<T>{
    private:
    	using SquareMatrixBase<T>::invert;//避免遮掩base版的invert
    public:
    	void invert(){
    		this->invert(n);//inline调用base版的invert
    	}
    };
    
    int main(){
    	SquareMatrix<double, 5> sm1;
    	sm1.invert();
    
    	SquareMatrix<double, 10> sm2;
    	sm2.invert();
    
    	system("pause");
    	return 0;
    }
    

    上述例子中,带参数的invert在base class SquareMatrixBase中,SquareMatrixBase只对矩阵对象的类型参数化,所以对于给定元素对象类型,所有矩阵共享同一个也是唯一一个SquareMatrixBase class,它们也将共享SquareMatrixBaseclass内的invert。

    这个例子要注意两点:

    a、使用using声明和this->指针

    b、这里的base class只是为了帮助derived class实现,并不是为了表现SquareMatrix和SquareMatrixBase之间的is-a关系,所以用private继承

    上述例子还有问题,SquareMatrixBase::invert如何知道该操作什么数据呢?数据放在哪里?令SquareMatrixBase贮存一个指针,指向矩阵数值所在的内存:

    #include<iostream>
    using namespace std;
    
    template<typename T>
    class SquareMatrixBase{
    public:
    	SquareMatrixBase(size_t n, T* pMem):size(n), pData(pMem){}
    	void setDataPtr(T* ptr){ pData = ptr; }
    	void invert(){
    		//运用size和pData求逆矩阵
    		cout << "求逆矩阵" << endl;
    	}
    private:
    	size_t size;//矩阵的大小
    	T* pData;//指向矩阵内容
    };
    template<typename T, size_t n>
    class SquareMatrix : private SquareMatrixBase<T>{
    public:
    	SquareMatrix():SquareMatrixBase<T>(n, data){}
    	void invert(){
    		//inline调用base版的invert
    		SquareMatrixBase<T>::invert();
    	}
    private:
    	T data[n*n];
    };
    
    int main(){
    	SquareMatrix<double, 5> sm1;
    	sm1.invert();
    
    	SquareMatrix<double, 10> sm2;
    	sm2.invert();
    
    	system("pause");
    	return 0;
    }
    

    上述做法对象不需要动态分配内存,但对象自身可能非常大。可以把每一个矩阵的数据放进heap

    #include<boostsmart_ptr.hpp>
    #include<iostream>
    using namespace std;
    using namespace boost;
    
    
    template<typename T>
    class SquareMatrixBase{
    public:
    	SquareMatrixBase(size_t n, T* pMem):size(n), pData(pMem){}
    	void setDataPtr(T* ptr){ pData = ptr; }
    	void invert(){
    		//运用size和pData求逆矩阵
    		cout << "求逆矩阵" << endl;
    	}
    private:
    	size_t size;//矩阵的大小
    	T* pData;//指向矩阵内容
    };
    template<typename T, size_t n>
    class SquareMatrix : private SquareMatrixBase<T>{
    public:
    	SquareMatrix():SquareMatrixBase<T>(n, 0),pData(new T[n*n]){
    		this->setDataPtr(pData.get());
    	}
    	void invert(){
    		//inline调用base版的invert
    		SquareMatrixBase<T>::invert();
    	}
    private:
    	scoped_array<T> pData;
    };
    
    int main(){
    	SquareMatrix<double, 5> sm1;
    	sm1.invert();
    
    	SquareMatrix<double, 10> sm2;
    	sm2.invert();
    
    	system("pause");
    	return 0;
    }
    

    上面只讨论了非类型模板参数带来的膨胀,类型参数也会导致膨胀。如许多平台上的int和long有相同的二进制表示,所以像vector<int>和vector<long>的成员函数有可能完全相同,这正是膨胀的最佳定义。在大多数平台上,所有的指针类型都有相同的二进制表述,因此凡templates持有指针者往往应该对每一个成员函数使用唯一一份底层实现。

    请记住:

    • Template生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
    • 因非类型模版参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
    • 因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

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

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

    真实指针做的很好的一件事是支持隐式转换。Derivedclass指针可以隐式转换为base class指针。指向non-const的对象的指针可以转换为指向const对象。

    但是同一个template的不同具现体之间并不存在什么与生俱来的固有关系(如果带有base-derived关系的B,D两类型分别具现化某个template,产生出的两个具现体并不带有base-derived关系),所以编译器视SmartPtr<Middle>和SmartPtr<Top>为完全不同的classes。为了获得我们希望的SmartPtr class之间的转换能力,我们必须将它们明确地编写出来。

    例子:

    //Smart.h
    #ifndef SMART_H
    #define SMART_H
    
    template<typename T>
    class SmartPtr{
    public:
    	SmartPtr(T* realPtr = 0); //构造函数
    	~SmartPtr();//析构函数
    
    	SmartPtr(const SmartPtr& rhs); //拷贝构造函数
    	template<typename U>
    	SmartPtr(const SmartPtr<U>& rhs);//泛化拷贝构造函数
    	SmartPtr& operator=(const SmartPtr& rhs);//拷贝赋值运算符
    	template<typename U>
    	SmartPtr& operator=(const SmartPtr<U>& rhs);//泛化拷贝赋值运算符
    
    	T* get() const; //获取原始指针
    	T* operator->() const;//重载->运算符
    	T& operator*() const;//重载*运算符
    	bool operator!() const;//重载!运算符
    private:
    	T* pointer;
    };
    //构造函数
    template<typename T>
    SmartPtr<T>::SmartPtr(T* realPtr = 0) :pointer(realPtr){}
    //析构函数
    template<typename T>
    SmartPtr<T>::~SmartPtr(){
    	delete pointer;
    }
    //拷贝构造函数
    template<typename T>
    SmartPtr<T>::SmartPtr(const SmartPtr<T>& rhs){
    	pointer = rhs.pointer;
    	rhs.pointer = 0;
    }
    //泛化拷贝构造函数
    template<typename T>
    template<typename U>
    SmartPtr<T>::SmartPtr(const SmartPtr<U>& rhs) :pointer(rhs.get()){
    }
    //拷贝赋值运算符
    template<typename T>
    SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T>& rhs){
    	if (this == &rhs)
    		return *this;
    	delete pointer;
    	pointer = rhs.pointer;
    	rhs.pointer = 0;
    	return *this;
    }
    //泛化拷贝赋值运算符
    template<typename T>
    template<typename U>
    SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<U>& rhs){
    	if (this == &rhs)
    		return *this;
    	delete pointer;
    	pointer = rhs.get();
    	return *this;
    }
    //获取原始指针
    template<typename T>
    T* SmartPtr<T>::get() const{
    	return pointer;
    }
    //重载->运算符
    template<typename T>
    T* SmartPtr<T>::operator->() const{
    	return pointer;
    }
    //重载*运算符
    template<typename T>
    T& SmartPtr<T>::operator*() const{
    	return *pointer;
    }
    //重载!运算符
    template<typename T>
    bool SmartPtr<T>::operator!() const{
    	if (pointer == nullptr)
    		return true;
    	return false;
    }
    
    #endif
    
    //Tmb.h
    #ifndef TMB_H
    #define TMB_H
    
    #include<iostream>
    class Top{
    public:
    	Top(int i = 0){ iTop = i; }
    	void printT(){ std::cout << iTop << std::endl; }
    private:
    	int iTop;
    };
    class Middle:public Top{
    public:
    	Middle(int i = 0):Top(i){}
    	void printM(){ printT(); }
    };
    class Bottom:public Middle{
    public:
    	Bottom(int i = 0):Middle(i){}
    	void printB(){ printM(); }
    private:
    	int iTop;
    };
    
    #endif
    #include"Smart.h"
    #include"Tmb.h"
    using namespace std;
    
    //main.cpp
    int main(){
    	Top* pt1 = new Middle;
    	Top* pt2 = new Bottom;
    	const Top* pct = pt1;
    
    	SmartPtr<Top> spt1 = SmartPtr<Middle>(new Middle);
    	SmartPtr<Top> spt2 = SmartPtr<Bottom>(new Bottom);
    	SmartPtr<const Top> spct = spt1;
    
    	//SmartPtr<Middle> spt3 = SmartPtr<Top>(new Top);//编译出错,无法从“Top *”转换为“Middle *”
    
    	system("pause");
    	return 0;
    }
    

    上述例子中,我们为SmartPtr写了泛化拷贝构造函数和泛化拷贝赋值运算符,为了SmartPtrclass之间能互相转换。

    上述例子要注意以下几点:

    a、我们希望根据SmartPtr<Middle>创建一个SmartPtr<Top>,但不希望一个SmartPtr<Top>创建一个SmartPtr<Middle>。上述泛化拷贝函数中用成员初始列来初始化SmartPtr<T>内类型为T*的成员变量,并以U*作为初值。这个行为只有当“存在某个隐式转化可将一个U*指针转换为一个T*指针”时才能通过编译,那正是我们想要的。

    b、在类内声明一个泛化拷贝构造函数(是个member template)并不阻止编译器生成它们自己的拷贝构造函数(一个non-template),所以如果你想要控制拷贝构造函数的方方面面,你必须同时声明泛化拷贝构造函数和“正常的”拷贝构造函数。相同规则也适用于赋值操作符。

    请记住:

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

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

  • 相关阅读:
    【YbtOJ#20238】最优路线
    【洛谷P3247】最小公倍数
    【洛谷P3261】城池攻占
    【YbtOJ#20236】红点蓝点
    【YbtOJ#20235】公共序列
    Wing IDE 4.1使用笔记一修正一下框框字体显示不了中文
    飘逸的python
    The 12th tip of DB Query Analyzer, powerful in text file process
    PHP网站如何解决大流量与高并发的问题
    【python】利用sftp及rsa密匙实现远程拷贝文件
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785434.html
Copyright © 2011-2022 走看看