《effective c++》学习笔记(七)
来源:互联网 发布:mac iphoto gif 编辑:程序博客网 时间:2024/06/05 11:20
了解隐式接口和编译期多态
查看下面这段代码:
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++程序可能在每一方面都更高效:较小的可执行文件、较短的运行期、较少的内存需求。
下面是一个计算
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;}
当程序编译成功的那一刻,
- Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率
- TMP可将用来生成“基于政策选择组合”的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码
- 《Effective C++》学习笔记(七)
- 《effective c++》学习笔记(七)
- Effective Java学习笔记(七)
- Effective C++ 学习笔记(七)
- Effective Java 学习笔记(六、七)
- Effective c++ 学习笔记(七)
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记
- 《Effective C++》学习笔记(1)
- 《Effective C++》学习笔记(一)
- 《Effective C++》学习笔记(二)
- 《Effective C++》学习笔记(三)
- 《Effective C++》学习笔记(四)
- 《Effective C++》学习笔记(五)
- 《Effective C++》学习笔记(六)
- 《Effective C++》学习笔记(八)
- xlistview
- muduo库整体架构简析
- xlistview
- Spring Redis与ActiveMQ发布订阅模式源码分析
- Python3:《机器学习实战》之朴素贝叶斯(3)过滤垃圾邮件
- 《effective c++》学习笔记(七)
- Junit 实例精讲基础教程(一) 认识Junit基本注解@Before、@After、@Test、@BeforeClass、@AfterClass
- 08-Object类
- LeetCode-22-Generate-Parentheses 记忆化搜索,Python类变量,set强转list
- HDU 6194 后缀自动机(2017 ICPC沈阳网络赛A题)
- 模仿豆瓣网做一个电影网站
- 2017年中级会计师考试真题(0909日)(回忆版)
- Jsp与JavaBean
- [BZOJ]1562: [NOI2009]变换序列 二分图匹配