您的位置:首页 > 编程语言 > C语言/C++

Effective C++ 条款 44、45

2018-03-30 11:55 344 查看

条款四十四:将与参数无关的代码抽离template

标题上说“将与参数无关的代码抽离template”,这里的参数既可以指类型,也可以是非类型,我们先来看看非类型的情况。

假定我们要为矩阵写一个类,这个矩阵的行列元素个数相等,是一个方阵,因而我们可以对之求逆运算。因为方阵的元素可以有多种类型,同时方阵的维数(方阵大小)也可以不同,像下面这样,我们使用了模板:

template <class T, size_t n>
class SquareMatrix
{
public:
void Invert();
};

int main()
{
SquareMatrix<int, 10> a;
SquareMatrix<int, 5> b;
}


模板既可以指定类型,也可以指定其他参量,比如这里的size_t,用来表示方阵的维数。程序看起来是没有问题的,编译也是通过的,但从代码优化上看,还是有空间可以做的。a和b虽然都是元素为int型的方阵,但因为方阵维数不同,因而生成了两个Invert函数,这两个Invert函数的代码只是在循环个数上不同(取觉于方阵维数n),但算法思想完全是一样的。

为了防止编译器生成冗余的代码,我们可以将Invert抽离这里,变成一个以n为参数的函数,像下面这样:

class SquareMatrixBase
{
public:
SquareMatrixBase(T* p) : DataPointer(p){}
void Invert(size_t n){}
private:
T* DataPointer;
};

template <class T, size_t n>
class SquareMatrix: private SquareMatrixBase<T>
{
public:
SquareMatrix() : SquareMatrixBase(Data)
{}
void Invert()
{
SquareMatrixBase::Invert(n);
}
private:
T Data[n * n];
};


这里我们定义了一个BaseSquareMatrix类,这个类可以视为一个工具类,因为SquareMatrix是private继承于它的,我们把算法一致、只与维数n相关的算法函数都放在BaseSquareMatrix中,且将n作为它的Invert()函数的形参,为了使BaseSquareMatrix可以访问数据,从而求逆运算,我们声明了它的成员指针,这个指针将会在构造时指向子类的数据存储空间。

子类SquareMatrix没有大变化,只是在Invert的时候,用了一句代码就搞定了——调用父类的Invert函数,并把维数n传过去。

这样在main函数中,针对类型都是int,但矩阵维数不同的情况,Invert中生成的冗余代码非常少(只有一句话),父类也因为模板参数与维数无法,所以也只生成一份。从这个角度来看,将与参数无关的代码抽离template起到了减少代码冗余度的优化作用。

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;       //指针,指向矩阵的内容,否则invert()不知道要操作什么类型的数据。有些实现版本也可以在类内部直接分配内存来初始化矩阵内容,也可以通过动态内存分配存储内容
};
template<typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T>{ //只是调用基类中的实现,而非为了表现出“is-a“关系,故使用私有继承
private:
using SquareMatrixBase<T>::invert;        //避免同名函数的名称遮掩
...
public:
void invert(){ this->invert(n);}     //内联函数,调用基类版本的invert(),同样this指针也是为了防止同名函数的遮掩
...
};


目前只是说将与类型无关的代码进行抽离(造一个父类,把算法相同的部分提取出来,放到父类),对于不同类型,其实也可以通过void*来实。比如STL,要想令我们使用
list<int*>,list<const int*>
生成的代码冗余度保持很低,这就需要在相应的函数里面,将之链到父类的一个公共实现版本里面,这个版本的形参是void*。

最后总结一下:

Template生成多个classes与多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。

因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或者class成员变量替换template参数。

因类型而造成的代码膨胀,也可以降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

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

比如有一个Base类和一个Derived类,像下面这样:

class BaseClass

{…};

class DerivedClass : public BaseClass

{…};

因为是父类与子类的关系,所以可以这样写:

DerivedClass *d;
BaseClass *b = static_cast< BaseClass *>d; // 用C风格直接是 b = (BaseClass*) d


我们可以弄一个简易的Shared型智能指针类,如果直接像下面这样写:

template <class T>
class SharedPtr
{
private:
T* Ptr;
static size_t Count;

public:
SharedPtr(T* _ptr)
{
Count = 1;
Ptr = _ptr;
cout << "Constructor Called Same" << endl;
}
~SharedPtr()
{
cout << "Destructor Called" << endl;
if (--Count == 0)
{
cout << "Delete Pointer" << endl;
delete Ptr;
Ptr = NULL;
}
}

SharedPtr(const SharedPtr<T>& _Smart)
{
cout << "Copy Constructor Called Same" << endl;
Ptr = _Smart.Ptr;
++Count;
}
};


那么显示编译器不会允许SharedPtr pb(pd),因为在编译期替换T时,拷贝构造函数明确了接受类型必须是SharedPtr,而由SharedPtr对象至SharedPtr的转换并不存在,所以编译器报错。

为了能使这样的转换合法,我们需要在原来的基础上这样写:

class SharedPtr
{
private:
T* Ptr;
static size_t Count;

public:
SharedPtr(T* _ptr)
{
Count = 1;
Ptr = _ptr;
cout << "Constructor Called Same" << endl;
}

template <class Other>
SharedPtr(Other* _ptr)
{
Count = 1;
Ptr = static_cast<T*> (_ptr);
cout << "Constructor Called Other" << endl;
}

~SharedPtr()
{
cout << "Destructor Called" << endl;
if (--Count == 0)
{
cout << "Delete Pointer" << endl;
delete Ptr;
Ptr = NULL;
}
}

T* GetPointer()
{
return Ptr;
}

dcc9
T* GetPointer() const
{
return Ptr;
}

SharedPtr(const SharedPtr<T>& _Smart)
{
cout << "Copy Constructor Called Same" << endl;
Ptr = _Smart.Ptr;
++Count;
}

template <class Other>
SharedPtr(const SharedPtr<Other>& _Smart)
{
cout << "Copy Constructor Called Other" << endl;
Ptr = static_cast<T*>(_Smart.GetPointer());
++Count;
}
};


注意代码标注为蓝色的部分(即为泛化部分),这里另外声明了一个模板参数Other,它可以与T相同,也可以不同,也就意味着它可以接受任何可以转化成T的类型了,比如父子类。这里还定义了GetPointer的方法,因为拷贝构建中传入的对象不一定是属于同一个类的,所以不能保证可以访问到类的私有成员。Ptr = static_cast

template<class Ty>
class shared_ptr {
public:
typedef Ty element_type;

shared_ptr();
shared_ptr(nullptr_t);
shared_ptr(const shared_ptr& sp);
shared_ptr(shared_ptr&& sp);
template<class Other>
explicit shared_ptr(Other * ptr);
template<class Other, class D>
shared_ptr(Other * ptr, D dtor);
template<class D>
shared_ptr(nullptr_t, D dtor);
template<class Other, class D, class A>
shared_ptr(Other *ptr, D dtor, A alloc);
template<class D, class A>
shared_ptr(nullptr_t, D dtor, A alloc);
template<class Other>
shared_ptr(const shared_ptr<Other>& sp);
template<class Other>
shared_ptr(const shared_ptr<Other>&& sp);
template<class Other>
explicit shared_ptr(const weak_ptr<Other>& wp);
template<class Other>
shared_ptr(auto_ptr<Other>& ap);
template<class Other, class D>
shared_ptr(unique_ptr<Other, D>&& up);
template<class Other>
shared_ptr(const shared_ptr<Other>& sp, Ty *ptr);
~shared_ptr();
shared_ptr& operator=(const shared_ptr& sp);
template<class Other>
shared_ptr& operator=(const shared_ptr<Other>& sp);
shared_ptr& operator=(shared_ptr&& sp);
template<class Other>
shared_ptr& operator=(shared_ptr<Other>&& sp);
template<class Other>
shared_ptr& operator=(auto_ptr< Other >&& ap);
template <class Other, class D>
shared_ptr& operator=(const unique_ptr< Other, D>& up) = delete;
template <class Other, class D>
shared_ptr& operator=(unique_ptr<Other, D>&& up);
void swap(shared_ptr& sp);
void reset();
template<class Other>
void reset(Other *ptr);
template<class Other, class D>
void reset(Other *ptr, D dtor);
template<class Other, class D, class A>
void reset(Other *ptr, D dtor, A alloc);
Ty *get() const;
Ty& operator*() const;
Ty *operator->() const;
long use_count() const;
bool unique() const;
operator bool() const;

template<class Other>
bool owner_before(shared_ptr<Other> const& ptr) const;
template<class Other>
bool owner_before(weak_ptr<Other> const& ptr) const;
template<class D, class Ty>
D* get_deleter(shared_ptr<Ty> const& ptr);
};


这里另外声明了一个模板参数Other,它可以与T相同,也可以不同,也就意味着它可以接受任何可以转化成T的类型了,比如父子类。这里还定义了GetPointer的方法,因为拷贝构建中传入的对象不一定是属于同一个类的,所以不能保证可以访问到类的私有成员。
Ptr = static_cast<T*>(_Smart.GetPointer())
这句话其实就是转换的实质了,只要任何可以转成T*的Other*,都是可以通过编译的,但如果是风马牛不相及的两个类,就不会通过编译。这里有一点要强调一下,我们可以把double转成int(窄化),也可以把int转成double(宽化),但注意double*与int*之间是不能相互转的,如果这样写int a = (int) (new double(2)),是不能通过编译的,可以用static_cast转换的类要有继承关系。代码还有赋值运算符也需要提供一个非泛化的版本和泛化的版本,这里简略没有写出。

这里还有一个需要注意的地方,在class类声明泛化copy构造函数(member template),并不会阻止编译器生成它们自己的copy构造函数(non-template),换言之,如果程序中只写了泛化的copy构造函数,那么编译器还是会自动生成一个非泛化的版本出来,如果不想要这个缺省版本,那一定不能偷懒,要两个版本的copy构造函数都要写。

最后总结一下:

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

如果你声明member templates用于“泛化copy构造”或“泛化assignment操作”,你还是需要声明正常的copy构造函数和copy assignment操作符。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: