《effective c++》学习笔记(七)
2017-09-10 21:56
295 查看
了解隐式接口和编译期多态
查看下面这段代码:template<typename T> bool foo(const T& lhs, const T& rhs) { if (lhs.bar() && rhs.bar()) { return true; } return false; }
当调用
foo()时,会根据参数的类型来实例化出函数,也就是我
foo()这个语句,可以调用不同的函数,这就是编译期多态。
而隐式接口则是规定调用
foo的参数必须要有bar这个成员函数,否则就会编译错误(实例化出函数后,编译错误)
classes和template都支持接口和多态
对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期
对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期
了解typename双重含义
对于一个template声明template<typename T> class Foo{ };
和
template<class T> class Foo{ };
是一模一样的,typename和class没有任何区别
但考虑下面这份代码:
template<typename T> class Foo{ public: typedef T value_type; }; template<typename T> void bar() { Foo<T>::value_type a; } int main() { bar<int>(); return 0; }
原因是template内出现的名称如果相依于某个template参数,称之为从属名称。如果从属名称在class内呈嵌套状,称之为嵌套从属名称。而嵌套从属名称,会被编译器认为不是一个类型,而是其他东西(比如一个static成员变量),这个时候需要在前面加一个typename:
template<typename T> class Foo{ public: typedef T value_type; }; template<typename T> void bar() { typename Foo<T>::value_type a; } int main() { bar<int>(); return 0; }
但这里有一个例外就是,typename不可以出现在base classes list内的嵌套从属名称类型名称之前,也不可以在member initialization list中作为base class修饰符
声明template参数时,前缀关键字class和typename可互换
请使用关键字typename标识嵌套从属类型名称:但不是在base class lists(基类列)或member initialization list内以它作为base class修饰符
学习处理模板化基类的名称
考虑下面这份代码:template<typename T> class Base { public: void foo() { } }; template<typename T> class Derived : Base<T> { public: void bar() { foo(); } };
一切看起来都很正常,但编译器会提示找不到foo这个函数的声明。。。
原因是因为编译器不会去找模板基类的名称,有三种办法可以解决这个问题:
添加this指针
this->foo();
显示调用
Base<T>::foo();
使用using
using Base<T>::foo;
-
可在derived class templates内通过”this->”指涉base classes template内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成
将与参数无关的代码抽离templates
考虑下面这份代码:template<typename T, size_t n> class Matrix{ public: Matrix() : _data(nullptr) { } private: shared_ptr<T> _data; };
当我定义了许多个不同的Matrix时
Matrix<int, 1> m1; Matrix<int, 2> m2; Matrix<int, 3> m3;
这份class就会实例化出3份class,最后程序的代码段会很长,充斥着重复代码,实际上完全可以把
size_t n当作一个成员变量来使用
template<typename T> class Matrix{ public: Matrix(size_t size) : _data(nullptr), _size(size) { } private: shared_ptr<T> _data; size_t _size; };
Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数相依关系
因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数
因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码
需要类型转换时请为模板定义非成员函数
考虑下面这份代码#include <bits/stdc++.h> using namespace std; template<typename T> class Foo{ public: Foo() = default; Foo(int x) { } }; template<typename T> const Foo<T> operator*(const Foo<T> &lhs, const Foo<T> &rhs) { Foo<T> ret; return ret; } int main() { Foo<int> f; 2 * f; return 0; }
我们的预期是在执行
2 * f时,2可以隐式转为Foo,然后和f相乘。但实际上这份代码不能通过编译,原因是因为编译器根据2推断出T是int类型,而int不能接受一个Foo。如果写成
f * 2,结果也是一样的,因为参数一个是Foo,一个是int,无法推断出来T是什么类型。
解决办法是,要让执行operator*时,函数已经被实例化了。做法是我们把operator*放在class内并声明一个friend就可以了
#include <bits/stdc++.h> using namespace std; template<typename T> class Foo{ friend const Foo operator*(const Foo &lhs, const Foo &rhs) { Foo ret; return ret; } public: Foo() = default; Foo(int x) { } }; int main() { Foo<int> f; 2 * f; return 0; }
这样的话当class被实例化时,这个函数也就被实例化了,当调用operator*时就会自动调这个friend函数。
最后还有一个问题就是class内的函数隐式inline,如果想要避免代码膨胀,可以使用这个class内部的friend函数调用一个non-member operator*辅助函数即可。
当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”
请使用traits classes表现类型信息
对于STL数据结构和算法,你可以使用五种迭代器。下面简要说明了这五种类型:Input iterators 提供对数据的只读访问。
Output iterators 提供对数据的只写访问
Forward iterators 提供读写操作,并能向前推进迭代器。
Bidirectional iterators提供读写操作,并能向前和向后操作。
Random access iterators提供读写操作,并能在数据中随机移动。
以std::advance为例,对于Random迭代器,我们可以直接进行
+=操作,对于其他类型,只能一步一步的
++操作,而在编译时获得模板类型可以使用traits
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_of_range("Nagative 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 classes使得“在编译期可用。它们以templates和”templates特化“来完成
整合重载技术后,traits classes有可能在编译期对类型执行if…else测试
认识template元编程
所谓模板元编程是以C++写成、执行与C++编译器内的程序。一旦TMP程序结束执行,其输出,也就是从templates具现出来的若干C++源码,便会一如既往地被编译。由于执行于C++编译期,因此可将工作从运行期转移到编译期。这导致的一个结果是,某些错误原本通常在运行期才能侦测到,现在可在编译期找出来。另一个结果是,使用TMP的C++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。
下面是一个计算N!的TMP程序:
template<size_t n> struct Fact { enum { value = Fact<n - 1>::value * n }; }; template<> struct Fact<0> { enum { value = 1 }; }; int main(int argc, char const* argv[]) { cout << Fact<10>::value << endl; //3628800 return 0; }
当程序编译成功的那一刻,10!已经被计算出来了,多么神奇的一件事情!
Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率
TMP可将用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码
相关文章推荐
- 《effective c++》学习笔记(一)
- 《effective c++》学习笔记(四)
- 《effective c++》学习笔记(五)
- 《effective c++》学习笔记(七)
- 《Effective C++ 》学习笔记——条款03
- 《Effective C++》学习笔记条款11 在operator =中处理“自我赋值”
- 《Effective C++》学习笔记——条款16
- 《Effective C++》学习笔记——条款32
- 《effective c++》学习笔记(一)
- 《effective c++》学习笔记(四)
- 《effective c++》学习笔记(五)
- 《effective c++》学习笔记(七)
- 《Effective C++》学习笔记——条款31
- 《effective c++》学习笔记(一)
- 《effective c++》学习笔记(四)
- 《effective c++》学习笔记(五)
- 《effective c++》学习笔记(七)
- 《Effective C++》学习笔记——条款37
- 《Effective C++》学习笔记——条款44
- 《Effective C++》学习笔记条款17 以独立语句将newed对象置入智能指针