More Related Content
Similar to C++模板与泛型编程 (15)
C++模板与泛型编程
- 11. 为什么要引入泛型 简单的一个开发场景说明 主角:小逗童鞋 1 简单的一个开发任务 任务内容:编写compare函数,分别比较int、string、double三种类型值的大小。要求比较的两个值通过参数传入compare函数中,如果第一个值大于第二个值,就然会1,如果小于就返回-1,否则返回0。 2 日本项目中抽取的一个实例 模板函数实例 3
- 12. 简单的一个案例分析 小逗提交了如下代码: int compare(const int& v1, cosnt int& v2) { if (v1 > v2) return 1; else if (v2 > v1) return -1; return 0; } int compare(const string& v1, const string& v2) { if (v1 > v2) return 1; else if (v2 > v1) return -1; return 0; } int compare(const double& v1, const double& v2) { if (v1 > v2) return 1; else if (v2 > v1) return -1; return 0; } 8
- 16. 简单的一个案例分析 日本项目的一个实例应用: 简单需求:处理市场发送过来的广播消息,消息数量大于有7,8种。 每种广播消息的格式的定义如下: typedef struct broadcast_message_type { broadcast_type_t broadcast_type; uint16_t items_n; char filler_2_s [2]; broadcast_message_type_item_t item [9]; 每个item对应一只股票。 } broadcast_message_type _t; 现在需求是,需要把每个广播消息中对应items下面的每一只股票发送给其他线程处理。 11
- 17. 简单的一个案例分析 发送消息的模板函数: template <typename msgT, typename memberT> void BOMXFFHBroadcastController::sendMessage( char* broadcast, ENDataEventType event_type) { msgT* message_serise = (msgT*)broadcast; char* data_ptr = broadcast; uint16_t items = message_serise->items_n; series_t series_key; data_ptr += offsetof(msgT, item); for (uint16_t index = 0; index < items; index++) { series_key = message_serise->item[index].series; sendDirectMessage(data_ptr, sizeof(memberT), event_type, series_key); data_ptr += sizeof(memberT); } } 12
- 18. 如何引入泛型 1、C++引入单根继承 2、参数化类型 实现通用容器的方案就是使用“参数化类型”。 一个容器需要能够存放任何类型的对象,那干脆就把这个对象的类型“抽”出来,参数化它: template<class T> class vector { T* v; int sz; public: vector(int); T& operator[](int); T& elem(int i) { return v[i]; } // ... }; 13
- 22. 为什么要引入泛型 这个算法有如下几个问题: 1. 类型安全性:使用者必须自行保证base指向的数组的元素类型和compar的两个参数的类型是一致的;使用者必须自行保证size必须是数组元素类型的大小。 2. 通用性:qsort对参数数组的二进制接口有严格要求——它必须是一个内存连续的数组。如果你实现了一个巧妙的、分段连续的自定义数组,就没法使用qsort了。 3. 接口直观性:如果你有一个数组char* arr = new arr[10];那么该数组的元素类型其实就已经“透露”了它自己的大小。然而qsort把数组的元素类型给“void”掉了(void *base),于是丢失掉了这一信息,而只能让调用方手动提供一个size。为什么要把数组类型声明为void*?因为除此之外别无它法,声明为任意一个类型的指针都不妥(compar的参数类型也是如此)。qsort为了通用性,把类型信息丢掉了,进而导致了必须用额外的参数来提供类型大小信息。在这个特定的算法里问题还不明显,毕竟只多一个size参数而已,但一旦涉及的类型信息多了起来,其接口的可伸缩性(scalability)问题和直观性问题就会逐渐显现。 4. 效率:compar是通过函数指针调用的,这带来了一定的开销。但跟上面的其它问题比起来这个问题还不是最严重的。 17
- 25. 函数模板(Function template)大解剖 定义C++ 模板的关键字 模板定义都以它开头。 template <typename T, size_t N> T sumArray(T (&arr)[N]) { T sum=0; for (int i = 0; i < N; ++i) { sum += arr[i]; } return sum; } 模板形参表 表示类型的类型形参(T) 表示常量表达式的非类型形参(N)。 模板函数体 在模板函数内部,可以使用类型形参 T 引用一个类型,T 表示哪种实际类型由编译器根据所用的函数而确定。 sumArray<int, 10>(array) <<--称之为模板的实例化(显示的模板实参),在编译阶段完成。 inline函数模板: template <typename T> inline T min(const T&, const T&); ○ inline template <typename T> T min(const T&, const T&); × 注意inline关键字的位置 非类型形参为啥是常量表达式? 19
- 26. 函数模板(Function template)大解剖 C++ Function Template Overloading template <class T> inline const T& min(const T& a, const T& b) { return b < a ? b : a; } 例如: r3 = min(r1, r2); 编译器做参数推导。 ■任何类型对象都可以 运用min(), 需要支持 operator<。 template <class T, class C> inline const T& min(const T& a, const T& b, C comp) { return comp(b, a) ? b : a; } 例如: r3 = min(r1, r2, compare); 编译器做参数推导。 ■需要根据类型自定义 比较函数,传入函数指针。 传入函数对象可以吗? 20
- 27. 类模板(class template)大解剖 定义C++ 类模板的关键字 类模板定义都以它开头。 template < typename T, size_t N = 32> class BFixedArray { public: explicit BFixedArray(size_t dim = N) : _cItems(dim), _buffer(_internal) { if(dim > THRESHOLD) { _buffer = new T[dim]; } } ~BFixedArray() { if (!isOnStack()) { delete[] _buffer; } } size_t size() const { return _cItems; } 模板形参表 表示类型的类型形参(T) 表示常量表达式的非类型形参(N)。 类模板定义体 在类模板内部,可以使用类型形参 T 引用一个类型,T 表示哪种实际类型由编译器根据所用的函数而确定。 编译器将使用用户提供的实际类型来实例化这个类模板。 BFixedArray<char, 128> sendbuffer(length); BFixedArray<int, 128> fixedBuffer(length); 类型形参由关键字class或typename后街说明符构成。在模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。 当然在模板的使用上,也是有不同之处(在模板定义内部指定类型) 21
- 28. 类模板(class template)大解剖 定义C++ 类模板的关键字 类模板定义都以它开头。 template <class _Tp, class _Alloc> void slist<_Tp,_Alloc>::remove(const _Tp& __val) { _Node_base* __cur = &this->_M_head; while (__cur && __cur->_M_next) { if (((_Node*) __cur->_M_next)->_M_data == __val) this->_M_erase_after(__cur); else __cur = __cur->_M_next; } } 类模板形参表 类模板成员函数作用域 类模板成员函数返回值 类模板成员函数定义 类模板成员函数 or 成员模板? 22
- 29. 类模板成员函数(class template member)大解剖 定义C++ 类模板的关键字 类模板定义都以它开头。 template <class _Tp, class _Alloc> void slist<_Tp,_Alloc>::remove(const _Tp& __val) { _Node_base* __cur = &this->_M_head; while (__cur && __cur->_M_next) { if (((_Node*) __cur->_M_next)->_M_data == __val) this->_M_erase_after(__cur); else __cur = __cur->_M_next; } } 类模板形参表 类模板成员函数作用域 类模板成员函数返回值 类模板成员函数定义 类模板成员函数 or 成员模板? 23
- 35. 编写泛型程序的简单原则 如何做两个 数字的比较呢 一般的程序思维里 if (v1 > v2) 或者 else if (v1 < v2) 但是在泛型思维中 if (v1 > v2) 或者 else if (v2 > v1) 类型只需要支持 > 而不必支持 < 简单原则:编写独立于类型的代码,对实参类型的要求尽可能少时有很有益的。 25
- 36. 编写泛型程序的简单原则 一般编程思维中的代码: template<typename T> int compare(const T& v1, const T& v2) { if (v1 > v2) return 1; else if (v1 < v2) return -1; return 0; } 根据泛型编程原则重构代码: template<typename T> int compare(const T& v1, const T& v2) { if (v1 > v2) return 1; else if (v2 > v1) return -1; return 0; } 26
- 39. 模板编译模型 标准C++为编译模板代码定义了两种编译模型。 包含编译模型(Inclusion compilation model) 几乎所有的编译器都支持的编译模式。 简单的说,就是模板定义放在头文件中。如STL。 不过,头文件中提供模板定义存在几个缺点: 函数的实现细节暴露。 如果大量的模板定义放于头文件中,会增加文件间的编译依赖,增加编译时间。 分离编译模型(Separate compilation model) 只有一部分编译器支持。 简单说,模板的声明放于头文件中,定义放于源文件中。 此种组织形式,和我们常规的非内联函数的定义组织方式相同 29
- 40. 模板编译模型 export(可选) template <class elemType> void Array<elemType>::init( const elemType *array, int sz ) { if ( ! array ) { _size = 0; _ia = 0; } if ( sz < 1 ) sz = 1; _size = sz; _ia = new elemType[ _size ]; for ( int ix = 0; ix < _size; ++ix ) _ia[ ix ] = array[ ix ]; } 日本项目开发环境: perseus ose_omx_order_book_detail_server/src> gcc -v gcc version 3.3.3 (SuSE Linux) 30