《Effective C++》 总结篇(模板与泛型编程)

条款四十一:了解隐式接口的和编译期多态

和面向对象编程的显示接口和运行期多态不同,
泛型编程更多是隐式接口和编译期多态。

#include <iostream>
using namespace std;
class A
{
public:
	A() { x = 12; cout << "Create A" << endl; }
	virtual std::size_t size() const { return x; }
	virtual void Init() { x = 10; cout << "Init A" << endl; }
	void swap(A& other) { int t = x; x = other.x; other.x = t; cout << "Swap" << endl; }
	bool operator != (const int & x)
	{
		cout << "!=" << endl;
		return this->x != x;
	}
private:
	int x;
};
template<class T>
void Func(T& a)
{
	if (a.size() > 10 && a != 5)
	{
		T temp(a);
		temp.Init();
		temp.swap(a);
	}
}
int main()
{
	A a;
	Func(a);
	return 0;
}
/*
Create A
!=
Init A
Swap
*/

1.以不同的参数具现化会导致调用不同的函数,这便是所谓的编译期多态。
2.泛型编程必须支持对应的隐式转换,如果函数中使用到诸如,size(),Init(),swap(),!=

请记住:
classes和templates都支持接口和多态。
对classes而言接口是显示的,以签名函数为中心。多态则是virtual函数发生于运行期。
对template参数而言接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。

条款四十二:了解typename的双重意义

template<class T>
template<typename T>

没什么不同,但是c++并不总是把class和typename视为等价。

涉及嵌套从属名称时只能使用关键字typename:

(从属名称(dependent name):template内出现的名称(示例中的iter)如果相依于某个template参数(示例中的C)的名称。如果从属名称在class内呈嵌套状,则称为嵌套从属名称(nested dependent name)。const_iterator也是类。

#include <iostream>
using namespace std;
class A
{
public:
	typedef int const_iterator;
private:
	int x;
};
template<typename T>
void Func(T& x)
{
	T::const_iterator* x;
}
int main()
{
	A a;
	Func(a);
	return 0;
}

这段代码编译会有报错

为什么?
因为我们不知道T::const_iterator是不是一个类型。我们无法确定!
可能T的某个静态成员变量恰好叫const_iterator,或者x碰巧是个global变量名称呢?
那样的话上述代码甚至不再是声明一个local变量而是一个相动作!!!
(T::const_iterator)* (x)!!!
所以c++规定必须使用typename为前缀。

#include <iostream>
using namespace std;
class A
{
public:
	typedef int const_iterator;
private:
	int x;
};
template<typename T>
void Func(T& a)
{
	typename T::const_iterator* x;
}
int main()
{
	A a;
	Func(a);
	return 0;
}

任何时候当你想要在template中指涉一个从属类型名称,就必须在紧临它的前一个位置放上关键字typename。
但是有例外!typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization list(成员初值列)中作为base class 修饰符。

#include <iostream>
using namespace std;
template<typename T>
class Base
{
public:
	class Nested
	{
	public:
		Nested(int x) { cout << x << endl; }
	};
};

template<typename T>
class Dierived :public Base<T>::Nested
{
public:
	Dierived(int x) :Base<T>::Nested(x) {}
	int x;
};
int main()
{
	Dierived<int> d(10);
	return 0;
}
/*
输出:10
*/

即不能写成
typename Base::Nested

对于这种长名称我们可以通过typedef简写,这是非常常用的技巧,谁不想少写点单词呢?

template<typename T>
void Func(T& a)
{
	typedef typename T::const_iterator* iter;
	iter x;
}

请记住:
声明template参数时,前缀关键字class和tyoename可互换
请使用typename标识嵌套从属类型名称:但不得在base class lists(基类列)和member initalization list(成员初值列)内以它作为base class修饰符

条款四十三:学习处理模板化基类内的名称

看这一段代码。Send(info)无法通过编译

#include <iostream>
using namespace std;
class CompanyA
{
public:
	void Send(string msg)
	{
		cout << "A Send : " << msg << endl;
	}
};
class CompanyB
{ 
	void Send(string msg)
	{
		cout << "B Send : " << msg << endl;
	}
};
class MsgInfo
{
public:
	string msg;
};
template<typename Company>
class MsgSender
{
	void Send(const MsgInfo& info)
	{
		string msg;
		msg = info.msg;
		Company c;
		c.Send(msg);
	}
};
template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{
public:
	void SendLog(const MsgInfo& info)
	{
		//log
		Send(info);//编译器不知道Company是什么,所以无法知道MsgSender<Company>是什么
	}
};
int main()
{

	return 0;
}

问题在于,当编译器遭遇class template LoggingMsgSender定义式时,并不知道他继承的是什么样的class。主要是不知道Company是什么。更明确的说是无法确定有Send这个函数。

这涉及到模板特化的问题!
我们看一个例子,假设我们引入了一个模板全特化的MsgSender

class CompanyZ
{

};
template<>
//一个全特化的MsgSender
class MsgSender<CompanyZ>
//,它和一般template相同,差别只是没有Send
{
};

所谓的模板全特化:template MsgSender针对类型Companyz特化了,并且其特化是全面的,也就是说一旦类型参数被定义为Companyz,再没有其他template参数可供变化。
(说人话:模板参数列表中all的模板参数都用具体的类型标识)
当类模板/函数模板进行全特化之后,这个全特化后的类/函数就不是一个模板类/函数了
(因为全特化完成后,模板参数类型为空了,即template<>了,就是一个具体的类/函数)

那么我们再考虑之前的LoggingMsgSender

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{
public:
	void SendLog(const MsgInfo& info)
	{
		//log
		Send(info);//如果Company==Companyz这个函数不存在
	}
};

这就是为什么C++拒绝调用的原因:它知道base class template可能被特化,而那个特化版本可能不提供和一般性template相同的接口。

解决方案有三种:
1.加上“this->”

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{
public:
	void SendLog(const MsgInfo& info)
	{
		//log
		this->Send(info);
	}
};

2.使用using声明式

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{
public:
	using MsgSender<Company>::Send;
	void SendLog(const MsgInfo& info)
	{
		//log
		Send(info);
	}
};

3.明白指出被调用的函数位于base class内:

template<typename Company>
class LoggingMsgSender :public MsgSender<Company>
{
public:
	
	void SendLog(const MsgInfo& info)
	{
		//log
		MsgSender<Company>::Send(info);
	}
};

请记住:
可在derived class templates内通过”this->”指涉base class templates内的成员名称,或藉由一个明白写出的“base class 资格修饰符”完成。

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

使用template可能导致代码膨胀,二进制码会带着重复(或者几乎重复)的代码、数据,或两者。其结果有可能源码看起来合身而整齐,但目标码却不是这么回事。所以该条例用于我们解决template带来的代码膨胀问题。

参考这一篇

#include <iostream>
using namespace std;
template <class T>
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> //n*n的矩阵T类型的
{
public:
    SquareMatrix() : SquareMatrixBase<T>(Data)
    {}
    void Invert()
    {
        SquareMatrixBase<T>::Invert(n);//调父类函数
    }
private:
    T Data[n * n];
};

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

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

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

真实的指针考研支持隐式转换。但我们自己简单写的指针不能这样

#include <iostream>
using namespace std;
template <class T>
class SmartPtr
{
public:
    SmartPtr(T* p) : p(p) {}
private:
    T* p;
};

class A
{

};
class B :public A
{

};
int main()
{
    SmartPtr<A> p1=SmartPtr<B>(new B);//不行
    shared_ptr<A> p2 = shared_ptr<B>(new B);//可以
}

为什么?如果以带有base-derived关系的A和B类分别具现化某个template,产生出来的两个具现化并不带有base-derived的关系,所以编译器认为这是两个完全无关的类。
解决方案是生成一个member template copy构造函数

#include <iostream>
using namespace std;
template <class T>
class SmartPtr
{
public:
    template<typename U>
    SmartPtr(const SmartPtr<U> & other):p(static_cast<T*>(other.get()))  {
    }
    SmartPtr(T* p) : p(p) {}
    T* get() const
    {
        return p;
    }
private:
    T* p;
};

class A
{

};
class B :public A
{

};
int main()
{
    SmartPtr<A> p1=SmartPtr<B>(new B);//不行
    shared_ptr<A> p2 = shared_ptr<B>(new B);//可以
}

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

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

为什么唯有non-menber函数才有能力”在所有实参身上实施隐式类型转换”。

#include <iostream>
using namespace std;
template <typename T>
class A {
public:
    A(T t) {
        this->t = t;
    }
    A(const A<T>& e) {
        this->t = e.t;
    }
    const A<T>& operator=(const A<T>& e) {
        this->t = e.t;
        return *this;
    }
    template<typename T>
    const A<T> operator*(const A<T>& t1, const A<T>& t2) {
        return A<T>(t1.t*t2.t);
    }
private:
    T t;
};
int main() {
    A<int> t1(10);
    A<int> t2 = t1 * 10;//报错
    return 0;
}

上述失败的原因是编译器不知道我们想要调用哪个函数,它们试图想出什么函数名为operator*的template具现化。但它们必须要知道T是什么,它们没有这个能耐。

第一个参数t1是A类型的,但第二个参数是int类型,那么编译器如何推算出T?
只需要:template class内的friend声明式可以指涉某个特定函数。那意味class A可以声明operator*为friend函数。所以编译器总是能够在class A具现化时得知T。

#include <iostream>
using namespace std;
template <typename T>
class A {
public:
    A(T t) {
        this->t = t;
    }
    A(const A<T>& e) {
        this->t = e.t;
    }
    const A<T>& operator=(const A<T>& e) {
        this->t = e.t;
        return *this;
    }
    friend const A<T> operator*(const A<T>& t1, const A<T>& t2) {
        return A<T>(t1.t*t2.t);
    }
private:
    T t;
};
int main() {
    A<int> t1(10);
    A<int> t2 = t1 * 10;//没报错
    return 0;
}

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

条款四十七:请使用traits classes 表现类型信息

完全看不懂。
主要是搞不明白traits。大概好像是利用模板偏特化使得模板得到类型?
traits
模板与泛型
请记住:
Traits classes使得”类型相关信息”在编译期可用。它们以templates和“templates特化完成实现”。
整合重载技术后,traits classes有可能在编译期对类型执行if。。else测试

条款四十八:认识template元编程

Template metaprogramming(TMP 模板元编程)
它有两个伟大效力:
第一,它让某些事情更容易。
第二,由于TMP执行于C++编译期,因此可将工作从运行期转移到编译期。这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。然而将工作从运行期转移到编译期的另一个结果是,编译时间变长了。

好的,我们来写hello world==》阶乘

#include <iostream>
using namespace std;
template<unsigned n>
struct Factorial {
    enum { value = n * Factorial<n - 1>::value };
};
//特化模板,Factorial<0>的value值为1
template<>
struct Factorial<0> {
    enum { value = 1 };
};
int main() {
    Factorial<5> a;
    cout << a.value << endl;
    return 0;
}

有了这个TMP,只要你指涉Factorial::value就可以得到n阶乘值。循环发生在template具现体内部指涉另一个template具现体Factional之时。我们需要一个特殊情况造成递归结束,这里的特殊情况是template特体化Factorial<0>。

此外,为了领悟TMP之所以值得学习,很重要的一点是先对它能够达成什么目标有一个比较好的理解,下面是三个例子:
第一、确保度量单位正确。如果使用TMP,就可以确保(在编译器)程序中所有量度单位的组合都正确,不论其计算多么复杂。这也是为什么TMP可被用来进行早期错误侦测。
第二,可以优化矩阵运算。如果使用高级、与TMP相关的template技术,即所谓的expression templates,就有可能消除多个矩阵相乘时产生的临时对象并合并循环。
第三,可以生成客户定制的设计模式实现品。运用所谓policy-based design的TMP-based技术,有可能产生一些templates用来表述独立的设计选项,然后可以任意结合他们,导致模式实现品带着客户定制的行为。此外也可以用来避免生成对某些特殊类型并不合适的代码。

请记住:
TMP可将工作由运行期转移到编译期,因而得以实现早期错误侦测或者更高的执行效率。
TMP可被用来生成“基于政策选择组合”的客户定制代码,也可以用来避免生成对某些特殊类型并不适合的代码。(这句话看不懂也没关系)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值