转自 http://blog.csdn.net/zhoudaxia/article/details/4531761
1、独占型智能指针: HolderPtr<T>。智能指针对象独占被管理对象的所有权,不允许对智能指针进行拷贝和赋值。可以提供一个release()函数来让出所有权。
- //holderptr.hpp:独占型智能指针,不允许复制和赋值的智能指针
- #ifndef HOLDERPTR_HPP
- #define HOLDERPTR_HPP
- template<typename T>
- class HolderPtr{
- private:
- T* ptr; //指向它所持有的对象(前提是该对象存在)
- public:
- HolderPtr():ptr(0){ //缺省构造函数:指向空对象
- }
- explicit HolderPtr(T* p):ptr(p){ //针对内建指针的构造函数,禁止隐式转型
- }
- ~HolderPtr(){
- if(ptr)
- delete ptr;
- }
- HolderPtr<T>& operator=(T* p){ //针对内建指针的赋值运算符
- if(ptr) delete ptr; //删除原来持有的对象
- ptr=p;
- return *this;
- }
- T& operator*(){ //指针运算符
- return *ptr;
- }
- T& operator*() const{ //const版本
- return *ptr;
- }
- T* operator->(){ //箭头运算符
- return ptr;
- }
- T* operator->() const{ //const版本
- return ptr; //在返回类型中,T本身可以是const类型,故用T*而不用T const*
- }
- T* get() const{ //获取里面的内建指针
- return ptr;
- }
- T* release(){ //释放对持有对象的所有权
- T* ret=ptr;
- ptr=0; //这样本holder不再持有对象,即使析构也无影响(ptr变成NULL)
- return ret; //返回新的持有对象的指针
- }
- void exchange_with(HolderPtr<T>& h){ //与另一个holder交换对象所有权
- swap(ptr,h.ptr);
- }
- void exchange_with(T*& p){ //与其他的内建指针交换对象所有权
- swap(ptr,p);
- }
- void reset(T* p=0){ //重置为一个新的指针
- if(ptr!=p){
- delete ptr;
- ptr=p;
- }
- }
- private:
- //禁止对智能指针的拷贝和赋值
- HolderPtr(HolderPtr<T> const&);
- HolderPtr<T>& operator=(HolderPtr<T> const&);
- void swap(T*& lhs,T*& rhs){ //指针交换函数
- T* tmp=lhs;
- lhs=rhs;
- rhs=tmp;
- }
- };
- #endif
2、转移型智能指针: AutoPtr<T>。允许对智能指针进行拷贝或赋值,但拷贝或赋值后对象所有权就会自动转移到其他的智能指针,原来的智能指针指向NULL。例如标准库中的std::auto_ptr就属于这一类智能指针。
- //autoptr.hpp:转移型智能指针
- #ifndef AUTOPTR_HPP
- #define AUTOPTR_HPP
- template<typename T>
- class AutoPtr{
- private:
- T *pointee; //指向持有的对象
- template<typename U>
- friend class AutoPtr;
- public:
- explicit AutoPtr(T* p=0):pointee(p){
- }
- AutoPtr(AutoPtr<T>& rhs):pointee(rhs.release()){ //默认的拷贝构造函数
- //参数不能为const的,因为转移对象的所有权需要修改指针成员的值
- }
- //针对其他兼容类型的拷贝构造函数
- template<typename U>
- AutoPtr(AutoPtr<U>& rhs):pointee(rhs.release()){
- }
- AutoPtr<T>& operator=(AutoPtr<T>& rhs){ //默认的赋值运算符
- if(this!=&rhs) reset(rhs.release()); //转移对象的所有权
- return *this;
- }
- //针对其他兼容类型的赋值
- template<typename U>
- AutoPtr<T>& operator=(AutoPtr<U>& rhs){
- if(this->pointee!=rhs.pointee) reset(rhs.release());
- return *this;
- }
- AutoPtr<T>& operator=(T* p){ //针对内建指针的赋值运算符
- if(pointee) delete pointee; //删除原来持有的对象
- pointee=p;
- return *this;
- }
- ~AutoPtr(){
- if(pointee) delete pointee;
- }
- T& operator*() const{ //重载解引用运算符
- return *pointee;
- }
- T& operator*(){ //非const版本
- return *pointee;
- }
- T* operator->() const{ //重载箭头操作符
- return pointee;
- }
- T* operator->(){ //非const版本
- return pointee;
- }
- T* get() const{
- return pointee;
- }
- T* release(){ //释放对象的所有权
- T* old=pointee;
- pointee=0;
- return old;
- }
- void reset(T* p=0){ //重置为一个新指针
- if(pointee!=p){
- delete pointee;
- pointee=p;
- }
- }
- };
- #endif
注意在拷贝和赋值时,函数的参数不能加const修饰符,因为转移对象的所有权需要修改参数的指针成员的值。另外要注意针对其他兼容类型的拷贝构造函数和赋值操作符使用了成员函数模板,它并不会覆盖默认的拷贝构造函数和赋值运算,因此我们还要显示提供默认的拷贝构造函数和赋值操作符,因为它们都是深拷贝和深赋值。
3、共享型智能指针: CountingPtr<T,CP,OP>。多个智能指针可以指向一个对象。这要对共享的指针个数进行计数,当计数器变为0时,就释放指向的对象。有多种计数策略(比如侵入式和非侵入式)和对象释放策略(比如对普通对象和数组对象释放策略不一样,有时可能必须要用free()来释放内存等),因此通常可以把计数功能和对象释放功能抽离出来设计成独立的计数policy和对象释放policy,通过模板参数传递给智能指针。我们这里把policy设计成具有成员函数模板的普通类,而不是模板,这样传递policy时就不需要模板模板参数,用普通的类型参数就可以了。
非侵入式计数器:计数器并不存储在所指向的对象内部,而是用独立的空间来存储和管理计数器。由于计数器对象比较小,又要被多个共享型智能指针所共享,为了提高性能,通常设计一个独立的分配器来分配大小固定的计数器对象。计数器policy类的操作包括分配、释放、加1、减1、判0等,设计成成员函数模板,形参为模板参数的指针类型,表示要计数的指针。代码如下:
- //simpleallocator.hpp:简单的计数器内存分配器,这里只分配和管理size_t大小的内存空间
- #ifndef SIMPLE_ALLOCATOR_HPP
- #define SIMPLE_ALLOCATOR_HPP
- #include <cstddef>
- size_t* alloc_counter(){ //分配size_t大小的内存
- return ::new size_t;
- }
- void dealloc_counter(size_t* ptr){ //释放内存
- ::delete ptr;
- }
- #endif
- //simplerefcount.hpp:简单的非侵入式计数器
- #ifndef SIMPLE_REFCOUNT_HPP
- #define SIMPLE_REFCOUNT_HPP
- #include <cstddef> //用到size_t的定义
- #include "simpleallocator.hpp"
- class SimpleReferenceCount {
- private:
- size_t* counter; // 分配的计数器
- public:
- SimpleReferenceCount () {
- counter = NULL;
- }
- // 缺省的拷贝构造函数和赋值运算符都是允许的,
- // 因为它们只是拷贝这个共享计数器
- template<typename T> void init (T*) { //分配计数器,并初始化为1
- counter = alloc_counter(); //形参T*表示要计数的指针
- *counter = 1;
- }
- template<typename T> void dispose (T*) { //释放计数器
- dealloc_counter(counter);
- }
- template<typename T> void increment (T*) { //计数值加1
- ++*counter;
- }
- template<typename T> void decrement (T*) { //计数值减1
- --*counter;
- }
- template<typename T> bool is_zero (T*) { //检查计数值是否为0
- return *counter == 0;
- }
- };
- #endif
侵入式计数器:将计数器放到被管理对象本身的类型中,是这个类型的一个普通成员变量,因此无需独立的分配器。侵入式计数器一般专用于管理某一种具体类型。计数器类应设计成模板,用一个模板参数来传递用作为计数器的成员变量指针。代码如下:
- //memberrefcount.hpp:侵入式计数器
- #ifndef MEMBER_REFCOUNT_HPP
- #define MEMBER_REFCOUNT_HPP
- template<typename ObjectT, // 计数器所在对象(即被智能指针管理的对象)的类型
- typename CountT, // 计数器的类型
- CountT ObjectT::* CountP> // 计数器所在位置(目标类型的成员指针)
- class MemberReferenceCount{
- public:
- // 缺省构造函数和析构函数都是允许的
- void init (ObjectT* object) { //对象的计数器的值初始化为1
- object->*CountP = 1;
- }
- void dispose (ObjectT*) { //释放计数器,ObjectT*是要计数的对象指针
- //并不需要执行任何操作
- //因为计数器是目标类型的一个普通成员变量
- //会随着目标对象的析构而自动释放
- }
- void increment (ObjectT* object) { //计数值加1
- ++(object->*CountP); //->*的优先级低于++
- }
- void decrement (ObjectT* object) { //计数值减1
- --(object->*CountP);
- }
- bool is_zero (ObjectT* object) { //检查计数值是否为0
- return object->*CountP == 0;
- }
- };
- #endif
对象释放器:对普通对象用delete,对数组对象用delete[],有时我们可能必须要使用其他的方式来释放对象,比如用C函数free()。我们把它们设计不同的policy类。如下:
- //objpolicies.hpp:对象释放器,根据对象的不同的类型,有不同的释放策略
- #ifndef OBJPOLICIES_HPP
- #define OBJPOLICIES_HPP
- class StandardObjectPolicy{ //普通型对象的释放
- public:
- template<typename T> void dispose (T* object){
- delete object;
- }
- };
- class StandardArrayPolicy { //数组对象的释放
- public:
- template<typename T> void dispose (T* array){
- delete[] array;
- }
- };
- //其他对象释放策略,比如直接用free()等
- //...
- #endif
使用了计数policy和对象释放policy的共享型智能指针实现如下:
- //countingptr.hpp:共享型智能指针,当对象的引用计数器变为0时,就删除指向的对象
- #ifndef COUNTINGPTR_HPP
- #define COUNTINGPTR_HPP
- #include "simplerefcount.hpp"
- #include "objpolicies.hpp"
- template<typename T,
- typename CounterPolicy = SimpleReferenceCount,
- typename ObjectPolicy = StandardObjectPolicy>
- class CountingPtr : private CounterPolicy, private ObjectPolicy { //模板参数作为基类
- private:
- typedef CounterPolicy CP; //定义简单的别名:计数器的类型
- typedef ObjectPolicy OP; //对象释放器的类型
- T* object_pointed_to; //所引用的对象(如果没有则为NULL)
- template<typename T2,typename CP2,typename OP2>
- friend class CountingPtr; //用于从兼容类型进行拷贝,要访问兼容类型的私有成员
- /*
- class BoolSupport{ //私有嵌套类,用于支持智能指针转型为成员指针,
- //从而可直接隐式转型为bool类型
- int dummy;
- };
- */
- void init (T* p) { // 用普通指针进行初始化
- if (p != NULL) {
- CounterPolicy::init(p);
- }
- this->object_pointed_to = p;
- }
- //拷贝用的成员函数模板:拷贝指针并且增加被拷贝指针的计数值
- template<typename S>
- void attach (CountingPtr<S,CP,OP> const& cp) {
- //拷贝内建的指针
- this->object_pointed_to = cp.object_pointed_to;
- if (cp.object_pointed_to != NULL) {
- //增加被拷贝的智能指针的引用计数
- CP::increment(cp.object_pointed_to);
- }
- }
- void detach() { // 减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
- if (this->object_pointed_to != NULL) { //必须要检查是否为空
- //减少本类的指针引用计数
- CounterPolicy::decrement(this->object_pointed_to);
- if (CounterPolicy::is_zero(this->object_pointed_to)) {
- CounterPolicy::dispose(this->object_pointed_to); //使用计数器来释放计数器
- ObjectPolicy::dispose(this->object_pointed_to); //使用对象释放器来释放对象
- }
- }
- }
- public:
- CountingPtr() { //缺省构造函数
- this->object_pointed_to = NULL;
- }
- explicit CountingPtr(T* p) { //针对内建指针的构造函数
- this->init(p); //使用普通指针初始化
- }
- template<typename U>
- explicit CountingPtr(U* p){ //针对派生类指针的构造函数模板
- this->init(p); //使用派生类指针初始化
- }
- CountingPtr(CountingPtr<T,CP,OP> const& cp) //缺省拷贝构造函数
- : CP((CP const&)cp), //拷贝基类部分的计数策略和对象释放策略
- OP((OP const&)cp) {
- this->attach(cp); //复制指针并增加被拷贝的智能指针计数值
- }
- //对兼容类型的拷贝构造函数模板,支持了隐式转型,
- //当T是void类型,或者是S的基类时,可从兼容类型转型为本类型
- template<typename S>
- CountingPtr(CountingPtr<S,CP,OP> const& cp)
- :CP((CP const&)cp),
- OP((OP const&)cp),
- object_pointed_to(cp.object_pointed_to){
- if(cp.object_pointed_to!=NULL)
- //增加被拷贝的智能指针计数值
- CP::increment(cp.object_pointed_to);
- }
- CountingPtr<T,CP,OP>&
- operator= (CountingPtr<T,CP,OP> const& cp) { //缺省的赋值运算符
- if (this->object_pointed_to != cp.object_pointed_to) {
- this->detach(); //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
- CP::operator=((CP const&)cp); //对基类部分中的计数策略和对象释放策略进行赋值
- OP::operator=((OP const&)cp);
- this->attach(cp); //拷贝指针并且增加被赋值的智能指针计数值
- }
- return *this;
- }
- template<typename S>
- CountingPtr<T,CP,OP>&
- operator= (CountingPtr<S,CP,OP> const& cp) { //对兼容类型的赋值运算符模板
- if (this->object_pointed_to != cp.object_pointed_to) {
- this->detach(); //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
- CP::operator=((CP const&)cp); //对基类部分中的计数策略和对象释放策略进行赋值
- OP::operator=((OP const&)cp);
- this->attach(cp); //拷贝指针并且增加被赋值的智能指针的引用计数值
- }
- return *this;
- }
- CountingPtr<T,CP,OP>& operator=(T* p) { //针对内建指针的赋值运算符
- if(p != this->object_pointed_to){ //计数指针不能指向*p
- this->detach(); //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
- this->init(p); //用一个普通指针进行初始化
- }
- return *this;
- }
- template<typename U>
- CountingPtr<T,CP,OP>& operator=(U* p){ //针对派生类指针的赋值运算符模板
- if(p != this->object_pointed_to){ //计数指针不能指向*p
- this->detach(); //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
- this->init(p); //用一个派生类指针进行初始化
- }
- return *this;
- }
- ~CountingPtr() { //析构函数
- this->detach(); //减少本类指针引用计数值,如果变成0,则释放该计数器和管理的对象
- }
- T* operator->(){ //重载箭头运算符
- return this->object_pointed_to;
- }
- T* operator->() const { //const版本
- return this->object_pointed_to;
- }
- T& operator*(){ //重载解引用运算符
- return *this->object_pointed_to;
- }
- T& operator*() const { //const版本
- return *this->object_pointed_to;
- }
- /*
- //到成员指针的转型,从而可以直接隐式转型为bool类型
- operator BoolSupport::*() const{
- return this->object_pointed_to ? (&BoolSupport::dummy) : (BoolSupport::*)0;
- }
- */
- operator bool() const{ //直接的bool转型,会有一些副作用(比如导致两个智能指针可以相加)
- return this->object_pointed_to!=(T*)0;
- }
- //比较运算符:与内建指针的直接比较
- friend bool operator==(CountingPtr<T,CP,OP> const& cp,T const* p){
- return cp==p;
- }
- friend bool operator==(T const* p,CountingPtr<T,CP,OP> const& cp){
- return p==cp;
- }
- //其他的一些比较运算符
- //...
- };
- //两个智能指针之间的比较:用内联函数
- template<typename T1,typename T2,
- typename CP,typename OP>
- inline bool operator==(CountingPtr<T1,CP,OP> const& cp1,
- CountingPtr<T2,CP,OP> const& cp2){
- return cp1.operator->()==cp2.operator->();
- }
- #endif
有几点要注意:
(1)在detach()中,在减少计数值之前必须要检查智能指针是否为空,因为空指针并没有可关联的计数器。
(2)CountingPtr<T,CP,OP>直接继承了两种policy类,这样当policy类为空类时,就可以执行空基类优化。比如前面的侵入式计数器MemberReferenceCount就是一个空类,在CountingPtr中使用它时可以被优化掉,但非侵入式计数器SimpleReferenceCount不是空类(有一个指针成员),不能被优化。而对象释放器均为空类。可见一般非侵入式计数器会增加智能指针的大小(当然我们可以使用压缩存储技术来优化,这需要改变该policy类的设计),侵入式计数器则会增加被管理的对象的大小。
(3)计数器类中,函数模板的形参表示要进行计数的指针,但函数内部却并不需要用到这个指针,因为它只需要改变计数值即可。
(4)智能指针的常数性:与内建指针相比,X const*(即const X*)与CountingPtr<X const>对应,表示所指对象为常数。X* const与CountingPtr<X> const对应,表示指针本身为常数。因此在->和*重载中,const版本的返回类型是T*或T&,而不是T const*或T const&,因为T本身可以是const类型。
(5)智能指针的转型:内建指针可以隐式转型为基类指针、void*类型或bool类型。对智能指针,我们可以添加转型成员函数,以允许CountingPtr<T>转型为bool类型。我们也可以转型为CountingPtr<B>(通过单参数的拷贝构造函数完成),其中B为void类型或者为T的基类,使用这种转型时就不能用侵入式计数器,因为void类型没有计数器,且基类的计数器与子类的计数器通常也不同(各有各的计数器)。注意我们并不允许智能指针直接转型为内建指针,因为我们设计智能指针就是为了避免使用内建指针,而且如果允许的话,对智能指针进行运算(如cp[n],cp2-cp1),将很难确定其结果。对于bool转型,可直接提供operator bool()运算符,但这会有一定的副作用,比如导致两个智能指针可以相加了。我们可以换一种解决方案,提供一个到成员指针的转型运算符(这需要增加一个私有的嵌套类,注意嵌套类并不会增加智能指针CountingPtr的大小),当转型到成员指针后,可以自动地隐式转型为bool类型。注意成员指针与内建指针不同,并不能对成员指针进行算术运算,因此没有副作用。但是,我在gcc上测试出这种方案不能通过(代码中的这部分我注释掉了),有可能是因为gcc并不支持到成员指针的转型运算符。
(6)智能指针的比较:内建指针支持相等比较(==,!=)和排序比较(,<,<=等)。对智能指针,可以添加相应的比较运算符函数。要注意有智能指针和内建指针的直接比较,也有两个智能指针之间的比较。对于两个智能指针之间的比较,我们把它设计成了一个独立的全局函数模板(在类外面),允许兼容类型的智能指针进行比较。由于是全局的非成员函数,因此要显式加inline表示内联,以提高性能。
4、测试代码。 当智能指针使用侵入式计数器时,由于计数器是在目标类型对象的内部,用于专门对这个类型的对象进行计数。因此为了使用方便,我们通常把智能指针组合到目标类型中,成为它的一个类型成员(这并没有增加目标类型的大小),这样通过这个类型成员就可以方便的定义我们需要的智能指针。
- //smartptrtest.cpp:对各种智能指针的测试
- #include "holderptr.hpp"
- #include "autoptr.hpp"
- #include "countingptr.hpp"
- #include "memberrefcount.hpp"
- #include <iostream>
- class Something{ //测试类
- public:
- void perform() const{
- }
- };
- class Derived: public Something{ //测试类
- };
- class MyClass{ //使用侵入式计数器的测试类
- public:
- size_t ref_count; //计数器
- public:
- typedef CountingPtr<MyClass,
- MemberReferenceCount<MyClass,size_t,&MyClass::ref_count> >
- SmartPtr; //管理本对象的智能指针类型
- void perform() const{
- }
- size_t getCounter(){
- return ref_count;
- }
- //...
- };
- void read_something(Something* x){ //测试函数
- }
- Something* load_something(){ //测试独占型指针的获取和转移
- HolderPtr<Something> result(new Something);
- read_something(result.get());
- Something* ret=result.get();
- result.release();
- return ret;
- }
- void do_test_things(){ //测试各个智能指针
- HolderPtr<Something> first(new Something);
- first->perform(); //测试箭头操作符
- (*first).perform(); //测试解引用操作符
- AutoPtr<Something> second(new Something);
- second->perform();
- (*second).perform();
- AutoPtr<Something> tmp(second); //测试缺省的拷贝构造
- AutoPtr<Derived> a1(new Derived);
- AutoPtr<Something> b1(a1); //测试兼容类型的拷贝构造
- second=a1; //测试兼容类型的赋值
- CountingPtr<Something> third(new Something);
- third->perform();
- (*third).perform();
- CountingPtr<Something> fouth(third);
- CountingPtr<Derived> a2(new Derived);
- CountingPtr<Something> b2(a2); //测试兼容类型的拷贝构造
- third=a2; //测试兼容类型的赋值
- third=new Something; //测试内建指针的赋值
- CountingPtr<Something> comp1(new Derived); //测试针对派生类指针的构造函数
- CountingPtr<Something> comp2;
- comp2=new Derived; //测试针对派生类指针的赋值运算符
- if(a2) //测试bool转型
- std::cout<<"a2 is not empty."<<std::endl;
- if(a2==b2) //测试两个智能指针的内容是否相等
- std::cout<<"a2 is equal to b2."<<std::endl;
- CountingPtr<MyClass,
- MemberReferenceCount<MyClass,size_t,&MyClass::ref_count> >
- a3(new MyClass); //测试侵入式的智能指针
- a3->perform();
- MyClass::SmartPtr b3(a3);
- std::cout<<b3->getCounter()<<std::endl;
- }
- int main(){
- HolderPtr<Something> ptr(load_something());
- do_test_things();
- return 0;
- }
从代码中可以看出,类型MyClass使用了侵入式计数器,因此我把管理这个类型的智能指针CountingPtr组合进来,定义成一个简洁的名字。在下面的测试代码中我们就可以看出,使用MyClass::SmartPtr要比使用CountingPtr<MyClass,MemberReferenceCount<MyClass,size_t,&MyClass::ref_count> >简洁得多。
============================================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4538589
C++中的数组类型和容器类型都是存放同类型对象的,有一个标准库类型std::pair可以存放两个异类对象。借助模板,我们可以开发出存放异类对象的类型。
1、二元组类型: Duo<T1,T2>。存放两个异类对象的元组类型,类似于std::pair。设置和获取各个域的操作直接为Duo<T1,T2>的成员函数。make_duo函数用来方便地创建二元组对象,类似于make_pair。
- //duo.hpp:存放两个异类对象的二元组类型
- #ifndef DUO_HPP
- #define DUO_HPP
- template <typename T1, typename T2>
- class Duo {
- public:
- typedef T1 Type1; // 用于访问第1个域的类型
- typedef T2 Type2; // 用于访问第2个域的类型
- enum { N = 2 }; // 域的个数
- private:
- T1 value1; // 第1个域的值
- T2 value2; // 第2个域的值
- public:
- Duo() : value1(), value2() { //缺省构造函数
- }
- Duo (T1 const& a, T2 const& b)
- : value1(a), value2(b) {
- }
- // 针对兼容类型的拷贝构造函数,可进行隐式类型转换
- template <typename U1, typename U2>
- Duo (Duo<U1,U2> const& d)
- : value1(d.v1()), value2(d.v2()) {
- }
- // 针对兼容类型的赋值运算符,可进行隐式类型转换
- template <typename U1, typename U2>
- Duo<T1, T2>& operator = (Duo<U1,U2> const & d) {
- value1 = d.value1;
- value2 = d.value2;
- return *this;
- }
- // 访问域的函数
- T1& v1() { //因为返回非const引用,可以修改对象的value1成员,因此函数
- //也必须是非const的
- return value1;
- }
- T1 const& v1() const { //const版本
- return value1;
- }
- T2& v2() {
- return value2;
- }
- T2 const& v2() const {
- return value2;
- }
- };
- // 两个二元组的比较运算符(允许混合类型)
- template <typename T1, typename T2,
- typename U1, typename U2>
- inline
- bool operator==(Duo<T1,T2> const& d1, Duo<U1,U2> const& d2){
- return d1.v1()==d2.v1() && d1.v2()==d2.v2(); //相等比较
- }
- template <typename T1, typename T2,
- typename U1, typename U2>
- inline
- bool operator!=(Duo<T1,T2> const& d1, Duo<U1,U2> const& d2){
- return !(d1==d2); //不等比较,可利用上面的相等比较运算符
- }
- // 包装函数:用来创建和初始化二元组的函数
- template <typename T1, typename T2>
- inline
- Duo<T1,T2> make_duo(T1 const & a, T2 const & b){
- return Duo<T1,T2>(a,b);
- }
- #endif
- //duotest.cpp:对duo的测试
- #include "duo.hpp"
- #include <iostream>
- Duo<float,int> foo (){
- return make_duo(42,42);
- }
- int main(){
- if (foo() == make_duo(42,42.0)) {
- std::cout<<"foo()==make_duo(42,42.0)"<<std::endl;
- }
- return 0;
- }
有几点要注意:
(1)我们用typedef定义了所存放的两个类型,这样就可以方便地访问两个类型。
(2)我们提供了针对兼容类型的拷贝构造函数和赋值运算符,从而可进行隐式类型转换。
(3)访问域的函数有非const版本和const版本。若函数返回非const引用,可以修改对象的value1成员,因此函数也必须是非const的,以表示它可以修改对象的值。
(4)比较运算符允许对混合类型进行比较。make_duo是包装函数,用来方便地创建和初始化二元组。
2、一般的元组类型: 可递归Duo<A, Duo<B,C> >类型表示一般的元组类,它使用模板元编程技术通过对Duo进行模板递归来实现。DuoT<N,T>用来获取元组类T中第N个域的类型,DuoValue<N,T>用来设置或获取元组类T中第N个域的值,它们也都使用了模板元编程技术来实现,用了很多模板特化来结束递归。val<N>(d)用来方便地设置和获取元组d的第N个域的值。
- //recduo.hpp:一般多元组类型,用模板元编程技术来实现(对Duo进行模板递归)
- #ifndef REC_DUO_HPP
- #define REC_DUO_HPP
- #include "duo.hpp" //普通的Duo二元组模板
- #include "typeop.hpp" //用TypeOp来确保返回需要的类型
- /*
- * Duo<A, Duo<B,C> >:用可递归Duo实现一般的多元组类型
- */
- template <typename A, typename B, typename C>
- class Duo<A, Duo<B,C> > { //对普通Duo的局部特化,用来递归地表示一般的多元组
- public:
- typedef A Type1; // 第1个域的类型
- typedef Duo<B,C> Type2; // 可递归Duo类型:包含了其他各个域
- enum { N = Duo<B,C>::N + 1 }; // 域的个数
- private:
- Type1 value1; // 第1个域的值
- Type2 value2; // 可递归Duo对象:包含了其他的域
- public:
- Duo() : value1(), value2() { //缺省构造函数
- }
- Duo (Type1 const& a, Type2 const& b)
- : value1(a), value2(b){
- }
- // 针对兼容类型的拷贝构造函数,可进行隐式类型转换
- template <typename U1, typename U2>
- Duo(Duo<U1,U2> const& d)
- : value1(d.v1()), value2(d.v2()) {
- }
- // 针对兼容类型的赋值运算符,可进行隐式类型转换
- template <typename U1, typename U2>
- Duo<Type1, Type2>& operator=(Duo<U1,U2> const & d) {
- value1 = d.value1;
- value2 = d.value2;
- return *this;
- }
- Type1& v1() { // 访问第1个域的函数
- return value1;
- }
- Type1 const& v1() const { //const版本
- return value1;
- }
- Type2& v2() { //访问可递归Duo对象
- return value2;
- }
- Type2 const& v2() const {
- return value2;
- }
- };
- template <typename A>
- class Duo<A,void> { //终止递归的局部特化:Duo中只有一个域
- public:
- typedef A Type1; // 第1个域的类型
- typedef void Type2; // 第2个域的类型,为void空类型
- enum { N = 1 }; // 域的个数
- private:
- Type1 value1; // 第1个域的值
- public:
- Duo() : value1() { //缺省构造函数
- }
- Duo (Type1 const & a)
- : value1(a) {
- }
- // 访问域的函数
- Type1& v1() {
- return value1;
- }
- Type1 const& v1() const { //const版本
- return value1;
- }
- void v2() { //因为只有一个域,故对第2个域不做任何事
- }
- void v2() const {
- }
- };
- /*
- * DuoT<N,T>:用来获取元组类T中第N个域的类型
- * 元组类可以是普通的Duo(二元组),也可以是可递归Duo(一般的多元组)
- */
- template <int N, typename T>
- class DuoT { //基本模板,获取第n个域的类型
- public:
- typedef void ResultT; // 一般情况下返回void空类型
- };
- template <typename A, typename B>
- class DuoT<1, Duo<A,B> > { //特化:返回普通Duo第1个域的类型
- public:
- typedef A ResultT;
- };
- template <typename A, typename B>
- class DuoT<2, Duo<A,B> > { //特化:返回普通Duo第2个域的类型
- public:
- typedef B ResultT;
- };
- template <typename A, typename B, typename C>
- class DuoT<1, Duo<A, Duo<B,C> > > { //特化:返回可递归Duo第1个域的类型
- public:
- typedef A ResultT;
- };
- template <typename A, typename B, typename C>
- class DuoT<2, Duo<A, Duo<B,C> > > { //特化:返回可递归Duo第2个域的类型
- public:
- typedef B ResultT;
- };
- template <int N, typename A, typename B, typename C>
- class DuoT<N, Duo<A, Duo<B,C> > > { //特化:返回可递归Duo第N(N>=3)个域的类型
- public:
- //它相当于第2个域(即Duo<B,C>)的第N-1个域的类型
- typedef typename DuoT<N-1, Duo<B,C> >::ResultT ResultT;
- };
- /*
- * DuoValue<N,T>:用来设置或者获取元组类T中第N个域的值
- * 元组类可以是普通的Duo(二元组),也可以是可递归Duo(一般的多元组)
- */
- template <int N, typename T>
- class DuoValue{ //基本模板
- public:
- static void get(T&){ // 一般情况下并不返回值
- }
- static void get(T const&){ //static成员函数不能有const修饰符
- }
- };
- template <typename A, typename B>
- class DuoValue<1, Duo<A, B> > { //特化:设置或获取普通Duo第1个域的值
- public:
- static A& get(Duo<A, B>& d) { //返回非const引用,可获取域的值,也可设置域的值
- //因此参数d也必须是变量型的(非const的)
- //否则d加const会使d.v1()会返回const引用
- return d.v1();
- }
- static A const& get(Duo<A, B> const& d){ //返回const引用,只获取域的值
- return d.v1();
- }
- };
- template <typename A, typename B>
- class DuoValue<2, Duo<A, B> > { //特化:获取普通Duo第2个域的值
- public:
- static B& get(Duo<A, B>& d) {
- return d.v2();
- }
- static B const& get(Duo<A, B> const& d){
- return d.v2();
- }
- };
- template <typename A, typename B, typename C>
- class DuoValue<1, Duo<A, Duo<B,C> > > { //特化:获取可递归Duo第1个域的值
- public:
- static A& get(Duo<A, Duo<B,C> > &d) {
- return d.v1();
- }
- static A const& get(Duo<A, Duo<B,C> > const &d) {
- return d.v1();
- }
- };
- template <typename A, typename B, typename C>
- class DuoValue<2, Duo<A, Duo<B,C> > > { //特化:获取可递归Duo第2个域的值
- public:
- static B& get(Duo<A, Duo<B,C> > &d) {
- return d.v2().v1();
- }
- static B const& get(Duo<A, Duo<B,C> > const &d) {
- return d.v2().v1();
- }
- };
- template <int N, typename A, typename B, typename C>
- class DuoValue<N, Duo<A, Duo<B,C> > > { //特化:获取可递归Duo第N(N>=3)个域的值
- public:
- //用TypeOp确保返回类型是非const的引用类型:用到了DuoT模板
- static typename TypeOp<typename DuoT<N-1, Duo<B,C>
- >::ResultT>::RefT
- get(Duo<A, Duo<B,C> >& d) {
- //它相当于获取第2个域(即Duo<B,C>)的第N-1个域的值
- return DuoValue<N-1, Duo<B,C> >::get(d.v2());
- }
- //用TypeOp确保返回类型是const引用类型
- static typename TypeOp<typename DuoT<N-1, Duo<B,C>
- >::ResultT>::RefConstT
- get(Duo<A, Duo<B,C> > const &d) {
- return DuoValue<N-1, Duo<B,C> >::get(d.v2());
- }
- };
- //包装函数:用于方便地设置或获取元组类第N个域的值
- //模板参数N要显式给出,A和B可通过演绎获得
- template <int N, typename A, typename B>
- inline typename TypeOp<typename DuoT<N, Duo<A, B>
- >::ResultT>::RefT
- val(Duo<A, B>& d){ //设置或者获取变量型duo的第N个值
- return DuoValue<N, Duo<A, B> >::get(d);
- }
- template <int N, typename A, typename B>
- inline typename TypeOp<typename DuoT<N, Duo<A, B>
- >::ResultT>::RefConstT
- val(Duo<A, B> const& d){ //只能获取常量型duo的第N个值
- return DuoValue<N, Duo<A, B> >::get(d);
- }
- #endif
- //recduotest.cpp:对用可递归Duo实现的一般多元组类进行测试
- #include "recduo.hpp"
- #include <iostream>
- int main()
- {
- // 创建和使用一个简单的duo
- Duo<bool,int> d;
- std::cout << d.v1() << std::endl;
- std::cout << val<1>(d) << std::endl;
- // 创建和使用三元组
- Duo<bool,Duo<int,float> > t;
- val<1>(t) = true;
- val<2>(t) = 42;
- val<3>(t) = 0.2;
- std::cout << val<1>(t) << std::endl;
- std::cout << val<2>(t) << std::endl;
- std::cout << val<3>(t) << std::endl;
- return 0;
- }
要注意的地方:
(1)非const版本的那个静态get(d)函数由于返回的是非const引用,通过这个引用可获取d中相应域的值,也可设置d中相应域的值,因此参数d也必须是非const的(即变量型的),否则如果d是const的,则不能为d中的域设置新值,另一方面它会使d.v1()会返回const引用,从而get(d)不能返回非const引用。d为const的那个get(d)函数返回const引用,只能用于获取相应域的值,但函数本身不能是const的,因为static成员函数不能同时又是const的。
(2)在特化版本DuoValue<N, Duo<A, Duo<B,C> > >中,我们用到了类型萃取技术中开发的TypeOp模板,这可确保返回类型是相应域类型的非const引用或const引用。
(3)val<N>(d)要提供两个版本,一个接受非const的引用参数d,可设置和获取d的第N个域的值,一个接受const引用参数d,只能获取d的第N个域的值,注意基于“引用型参数是否是const的”可以构成重载关系。
(4)元组类型Duo<A, Duo<B,C> >虽然理论上可以存放任意多个异类元素,但实际的编译器对模板嵌套深度是有限制的,因此这个元组类在实际应用时并不能存放太多个异类元素。
3、元素个数有限的元组类: 由于模板嵌套深度在实际中有限制,因此有时我们更愿意开发一个接口简单的、元素个数有限的元组类。Tuple<P1,P2,P3,P4,P5>就是这样一个元组类,它可存放1-5个域。也是用模板递归来实现的,Tuple继承自前面的可递归Duo类型,其中该Duo类型的域个数有限。
- //tuple.hpp:能存放1-5个元素的元组类型
- #ifndef TUPLE_HPP
- #define TUPLE_HPP
- #include "recduo.hpp" //可递归Duo类型
- #include "typeop.hpp" //用TypeOp来确保返回需要的类型
- class NullT { //代表无用类型
- };
- // 一般情况下,Tuple<>创建自“至少含有一个NullT的另一个Tuple<>“
- template <typename P1,
- typename P2 = NullT,
- typename P3 = NullT,
- typename P4 = NullT,
- typename P5 = NullT>
- class Tuple : public Duo<P1, typename Tuple<P2,P3,P4,P5,NullT>::BaseT> {
- public:
- //基类是一个可递归Duo类型
- typedef Duo<P1, typename Tuple<P2,P3,P4,P5,NullT>::BaseT>
- BaseT;
- Tuple() {} //缺省构造函数
- Tuple(typename TypeOp<P1>::RefConstT a1,
- typename TypeOp<P2>::RefConstT a2,
- typename TypeOp<P3>::RefConstT a3 = NullT(),
- typename TypeOp<P4>::RefConstT a4 = NullT(),
- typename TypeOp<P5>::RefConstT a5 = NullT())
- : BaseT(a1, Tuple<P2,P3,P4,P5,NullT>(a2,a3,a4,a5)) {
- }
- };
- // 结束递归的局部特化:Tuple有2个域,它派生自普通的Duo
- template <typename P1, typename P2>
- class Tuple<P1,P2,NullT,NullT,NullT> : public Duo<P1,P2> {
- public:
- typedef Duo<P1,P2> BaseT; //基类类型
- Tuple() {}
- Tuple(typename TypeOp<P1>::RefConstT a1,
- typename TypeOp<P2>::RefConstT a2,
- typename TypeOp<NullT>::RefConstT = NullT(),
- typename TypeOp<NullT>::RefConstT = NullT(),
- typename TypeOp<NullT>::RefConstT = NullT())
- : BaseT(a1, a2) {
- }
- };
- // 特化:Tuple只有1个域,它派生自只有一个域的Duo特化
- template <typename P1>
- class Tuple<P1,NullT,NullT,NullT,NullT> : public Duo<P1,void> {
- public:
- typedef Duo<P1,void> BaseT;
- Tuple() {}
- Tuple(typename TypeOp<P1>::RefConstT a1,
- typename TypeOp<NullT>::RefConstT = NullT(),
- typename TypeOp<NullT>::RefConstT = NullT(),
- typename TypeOp<NullT>::RefConstT = NullT(),
- typename TypeOp<NullT>::RefConstT = NullT())
- : BaseT(a1) {
- }
- };
- // 方便创建元组的make_tuple函数:1个实参的情况
- template <typename T1>
- inline
- Tuple<T1> make_tuple(T1 const& a1){
- return Tuple<T1>(a1);
- }
- // 方便创建元组的make_tuple函数:2个实参的情况
- template <typename T1, typename T2>
- inline
- Tuple<T1,T2> make_tuple(T1 const& a1, T2 const& a2){
- return Tuple<T1,T2>(a1,a2);
- }
- // // 方便创建元组的make_tuple函数:3个实参的情况
- template <typename T1, typename T2, typename T3>
- inline
- Tuple<T1,T2,T3> make_tuple(T1 const& a1, T2 const& a2,
- T3 const& a3){
- return Tuple<T1,T2,T3>(a1,a2,a3);
- }
- // 方便创建元组的make_tuple函数:4个实参的情况
- template <typename T1, typename T2, typename T3, typename T4>
- inline
- Tuple<T1,T2,T3,T4> make_tuple(T1 const &a1, T2 const &a2,
- T3 const &a3, T4 const &a4){
- return Tuple<T1,T2,T3,T4>(a1,a2,a3,a4);
- }
- // 方便创建元组的make_tuple函数:5个实参的情况
- template <typename T1, typename T2, typename T3,
- typename T4, typename T5>
- inline
- Tuple<T1,T2,T3,T4,T5> make_tuple(T1 const &a1, T2 const &a2,
- T3 const &a3, T4 const &a4,
- T5 const &a5){
- return Tuple<T1,T2,T3,T4,T5>(a1,a2,a3,a4,a5);
- }
- #endif
- //tupletest.cpp;对Tuple的测试
- #include "tuple.hpp"
- #include <iostream>
- int main()
- {
- //创建和使用只有一个域的tuple
- Tuple<int> t1;
- val<1>(t1) += 42;
- std::cout << t1.v1() << std::endl;
- //创建和使用duo
- Tuple<bool,int> t2;
- std::cout << val<1>(t2) << ", ";
- std::cout << t2.v1() << std::endl;
- //创建和使用triple
- Tuple<bool,int,double> t3;
- val<1>(t3) = true; //设置各个域的值
- val<2>(t3) = 42;
- val<3>(t3) = 0.2;
- std::cout << val<1>(t3) << ", ";
- std::cout << val<2>(t3) << ", ";
- std::cout << val<3>(t3) << std::endl;
- t3 = make_tuple(false, 23, 13.13); //在创建时给定各个域的值
- std::cout << val<1>(t3) << ", ";
- std::cout << val<2>(t3) << ", ";
- std::cout << val<3>(t3) << std::endl;
- //创建和使用quadruple
- Tuple<bool,int,float,double> t4(true,42,13,1.95583);
- std::cout << val<4>(t4) << std::endl;
- std::cout << t4.v2().v2().v2() << std::endl;
- return 0;
- }
有几点要注意:
(1)由于Tuple的域个数是可变的,因此我们声明了一个无用类型NullT来作模板参数的默认实参。这里并没有使用void类型,因为我们需要在构造函数中创建该类型的对象来作为函数形参的缺省值,而void类型不能创建对象。
(2)用typedef定义BaseT来表示它继承的可递归Duo类型。
(3)make_tuple用来方便地创建tuple元组。由于函数模板不能含有缺省模板实参,因此我们不能只写一个含有5个模板参数(通过给一些模板参数提供缺省实参)的make_tuple。如果想通过给函数的5个形参提供缺省调用实参来达到只编写一个make_tuple的愿望,这也不行,因为在演绎模板参数时,不会考虑缺省调用实参,这样就会导致使用缺省调用实参的模板参数演绎不出来,从而必须显式指定,使用起来不方便。总之,我们必须针对不同的域个数来定义相应的make_tuple。
=====================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4545727
C++的函数调用语法实体有函数、类似于函数的宏、函数指针、仿函数(即函数对象)。函数调用的方式有:
(1)直接调用:通过函数名直接调用函数,函数起始地址成为指令的一部分,因此编译期就能确定调用了哪个函数。
(2)间接调用:通过函数指针来调用函数,函数起始地址位于内存或寄存器的某处,因此到运行期才能确定调用哪个函数。
(3)内联调用:直接在调用处展开函数代码,在编译期进行,因此到运行期就不存在函数调用了。
对函数指针和仿函数的使用,基本上都是某种形式的回调。通常的程序库是库作者来编写库的代码,让用户来使用这些库,而回调正好相反,程序库调用了某个函数,而这个函数的代码需要由使用库的用户来编写。在实际应用中,仿函数与函数指针相比有很多优点,因为仿函数是类对象,因此我们可以在仿函数中关联状态信息(当然这会使仿函数类占内存空间,但我们可以使用空基类优化技术),这样根据不同的参数生成不同的函数实例。仿函数可以作为模板的类型实参来传递,也可以作为函数调用实参来传递。但仿函数是类对象,因此不能作为非类型模板实参,但函数指针可以作为非模板模板实参。通过仿函数,我们可以把所有的的普通函数都封装成仿函数类,这样就可以以对象的方式来统一使用各个函数。我们也可以对仿函数的各个参数类型和返回类型进行萃取,从而使其具有反射的能力,不同的仿函数还可以方便地组合起来使用。
编写仿函数类的基本约定: 在仿函数类中要定义仿函数的参数个数常量NumParams、每个参数的类型typedef Param1T,typedef Param2T,...、返回类型tyepdef ReturnT。根据约定,我们的仿函数类应该编写成类似于下面的形式:
- class PersonSort{
- public:
- enum(NumParams=2 };
- typedef bool ReturnT;
- typedef Person const& Param1T;
- typedef Person const& Param2T;
- bool operator()(Person const& p1,Person const& p2) const{
- //返回p1是否小于p2
- }
- };
1、仿函数的类型萃取实现: FunctorParam<F,N>用来获取仿函数F的第N的参数的类型,当N值不大于实际的参数个数时,调用UsedFunctorParam<F,N>模板针对N的特化来获取相应位置处的参数类型,当N值大于实际的参数个数时,返回私有的无用嵌套成员类 NNest,它不能用来创建对象,因此没有副作用。代码如下:
- //functorparam.hpp:获取仿函数各个参数的类型
- #ifndef FUNCTOR_PARAM_HPP
- #define FUNCTOR_PARAM_HPP
- #include "ifthenelse.hpp" //用到了IfThenElse模板
- /*
- * UsedFuctorParam<F,N>:抽取仿函数F的实际使用的第N个参数类型
- */
- template <typename F, int N>
- class UsedFunctorParam; //基本模板
- //对每个N值,都提供一个局部特化,以获取相应位置处参数的类型
- //使用宏FunctorParamSpec(N)来编写UsedFunctorParam的特化,可以使代码更简洁
- #define FunctorParamSpec(N) /
- template<typename F> /
- class UsedFunctorParam<F, N>{ /
- public: /
- typedef typename F::Param##N##T Type; /
- };
- //假设N到20为止,很少有多于20个参数的仿函数
- FunctorParamSpec(1)
- FunctorParamSpec(2)
- FunctorParamSpec(3)
- FunctorParamSpec(4)
- FunctorParamSpec(5)
- FunctorParamSpec(6)
- FunctorParamSpec(7)
- FunctorParamSpec(8)
- FunctorParamSpec(9)
- FunctorParamSpec(10)
- FunctorParamSpec(11)
- FunctorParamSpec(12)
- FunctorParamSpec(13)
- FunctorParamSpec(14)
- FunctorParamSpec(15)
- FunctorParamSpec(16)
- FunctorParamSpec(17)
- FunctorParamSpec(18)
- FunctorParamSpec(19)
- FunctorParamSpec(20)
- #undef FunctorParamSpec //宏FunctorParamSpec的生命期结束
- /*
- * FunctorParam<F,N>::Type:获取仿函数F的第N个参数的类型,当N大于实际参数
- * 个数F::NumParams时,FunctorParam<F,N>::Type会返回私有的无用嵌套类
- * NNest,它不能用来创建对象,因此没有副作用
- */
- template <typename F, int N>
- class FunctorParam{
- private:
- class Unused{ //嵌套类
- private:
- class NNest {}; //嵌套的无用成员类
- public:
- typedef NNest Type;
- };
- public:
- //获取相应位置处的参数类型
- typedef typename IfThenElse<F::NumParams>=N,
- UsedFunctorParam<F,N>,
- Unused>::ResultT::Type
- Type;
- };
- #endif
解释:
(1)UsedFuctorParam<F,N>用来抽取仿函数F的实际使用的第N个参数类型,这里N没有超过仿函数的实际参数个数。参数类型的抽取是通过对每个N值提供一个局部特化来实现的。假设仿函数的参数个数最多为20,对具有工业强度的库而言,其设计一般要达到20个参数左右。实际中很少有多于20个参数的仿函数。这里#undef表示宏FunctorParamSpec的生命期结束,即下面就已经不存在FunctorParamSpec这个宏了,要用的话需要重新定义。
(2)FunctorParam<F,N>中,N可以超过仿函数的实际参数个数,当超过时,就返回一个私有的无用嵌套类NNest,NNest在FunctorParam内部,在外部并不能直接用类名NNest来创建对象,因此对不存在的参数虽然返回了一个类型,但并没有产生副作用。这里用到了类型萃取技术中开发的IfThenElse模板。
2、把函数指针封装成仿函数: 直接对函数指针的返回类型和各个参数类型进行参数化,设计成仿函数类模板。
下面的ForwardParamT<T>用于决定仿函数的参数类型T是传引用还是传原来的普通类型,T为类类型时则传引用,其他情况则传原来的普通类型(这需要用到类型萃取技术中开发的TypeOp模板),这样可以避免昂贵的拷贝构造函数调用。对仿函数参数为空(即为void)的情况,提供一个全局特化,指定空参数的类型为一个无副作用的哑类型。
- //forwardparam.hpp:ForwardParamT<T>用于决定仿函数的参数类型T是传引用还是传原来的普通类型
- #ifndef FORWARD_PARAM_HPP
- #define FORWARD_PARAM_HPP
- #include "ifthenelse.hpp"
- #include "isclasst.hpp"
- #include "typeop.hpp" //用于决定函数的参数是传引用还是传原来的类型
- template<typename T>
- class ForwardParamT{ //基本模板:T为类类型则传const引用,否则就传普通类型
- public:
- typedef typename IfThenElse<IsClassT<T>::Yes,
- typename TypeOp<T>::RefConstT,
- typename TypeOp<T>::ArgT
- >::ResultT
- Type;
- };
- template<>
- class ForwardParamT<void>{ //全局特化:void表示函数参数为空,则传哑类型Ununsed,
- private: //它不能创建对象,因此没有副作用
- class Unused {};
- public:
- typedef Unused Type;
- };
- #endif
下面的FunctionPtrT用来根据函数指针的返回类型和各个参数类型构造出函数指针(这里针对最多为5个参数的情况,对含有更多个参数的函数原理和做法是一样的)。最后的FunctionPtr就是真正封装函数指针的仿函数类型,要遵循仿函数的编写约定。它组合一个用FunctionPtrT构造出来的函数指针,然后根据函数指针的不同实参个数(0-5个)重载operator(),并用函数指针来进行调用实际的函数即可。在调用函数时,用到了上面的ForwardParamT<T>,这样就可以在传递调用实参时避免拷贝构造函数的调用。包装函数func_ptr(*fp)用来方便地创建封装了函数指针的函数对象。
- //functionptr.hpp:把最多为5个参数的函数指针类型封装成仿函数模板FunctionPtr<RT,P1,P2,P3,P4,P5>
- //对含有更多个参数的函数,其原理和做法一样的
- #ifndef FUNCTION_PTR_HPP
- #define FUNCTION_PTR_HPP
- #include "forwardparam.hpp" //用于决定函数的形参T是传引用还是传原来的普通类型
- /*
- * FunctionPtrT<RT,P1,P2,P3,P4,P5>:用来根据返回类型和各个参数类型构造出函数指针
- */
- template<typename RT, typename P1 = void,
- typename P2 = void,
- typename P3 = void,
- typename P4 = void,
- typename P5 = void>
- class FunctionPtrT{ //基本模板:函数指针有5个参数的情况
- public:
- enum { NumParams = 5 }; //定义参数个数的枚举量
- typedef RT (*Type)(P1,P2,P3,P4,P5); //根据参数类型来定义函数指针
- };
- template<typename RT, typename P1,
- typename P2,
- typename P3,
- typename P4>
- class FunctionPtrT<RT,P1,P2,P3,P4,void>{ //局部特化:4个参数的情况
- public:
- enum { NumParams = 4 };
- typedef RT (*Type)(P1,P2,P3,P4);
- };
- template<typename RT, typename P1,
- typename P2,
- typename P3>
- class FunctionPtrT<RT,P1,P2,P3,void,void>{ //局部特化:3个参数的情况
- public:
- enum { NumParams = 3 };
- typedef RT (*Type)(P1,P2,P3);
- };
- template<typename RT, typename P1,
- typename P2>
- class FunctionPtrT<RT,P1,P2,void,void,void>{ //局部特化:2个参数的情况
- public:
- enum { NumParams = 2 };
- typedef RT (*Type)(P1,P2);
- };
- template<typename RT, typename P1>
- class FunctionPtrT<RT,P1,void,void,void,void>{ //局部特化:1个参数的情况
- public:
- enum { NumParams = 1 };
- typedef RT (*Type)(P1);
- };
- template<typename RT>
- class FunctionPtrT<RT,void,void,void,void,void>{ //局部特化:0个参数的情况
- public:
- enum { NumParams = 0 };
- typedef RT (*Type)();
- };
- /*
- * FunctionPtr<RT,P1,P2,P3,P4,P5>:封装函数指针的仿函数类型
- * 使用缺省的void时表示该参数为空
- */
- template<typename RT, typename P1 = void,
- typename P2 = void,
- typename P3 = void,
- typename P4 = void,
- typename P5 = void>
- class FunctionPtr{
- private:
- //获得相应的函数指针类型
- typedef typename FunctionPtrT<RT,P1,P2,P3,P4,P5>::Type FuncPtr;
- FuncPtr fptr; //被封装的函数指针
- public:
- //保存好参数个数及其各个参数类型
- enum { NumParams = FunctionPtrT<RT,P1,P2,P3,P4,P5>::NumParams };
- typedef RT ReturnT;
- typedef P1 Param1T;
- typedef P2 Param2T;
- typedef P3 Param3T;
- typedef P4 Param4T;
- typedef P5 Param5T;
- FunctionPtr(FuncPtr ptr):fptr(ptr) { //构造函数,创建函数对象,它封装了函数指针
- }
- // 下面是函数调用
- RT operator()() { //0个参数的情况
- return fptr();
- }
- RT operator()(typename ForwardParamT<P1>::Type a1){ //1个参数的情况
- return fptr(a1);
- }
- RT operator()(typename ForwardParamT<P1>::Type a1,
- typename ForwardParamT<P2>::Type a2){ //2个参数的情况
- return fptr(a1, a2);
- }
- RT operator()(typename ForwardParamT<P1>::Type a1,
- typename ForwardParamT<P2>::Type a2,
- typename ForwardParamT<P3>::Type a3){ //3个参数的情况
- return fptr(a1, a2, a3);
- }
- RT operator()(typename ForwardParamT<P1>::Type a1,
- typename ForwardParamT<P2>::Type a2,
- typename ForwardParamT<P3>::Type a3,
- typename ForwardParamT<P4>::Type a4){ //4个参数的情况
- return fptr(a1,a2,a3,a4);
- }
- RT operator()(typename ForwardParamT<P1>::Type a1,
- typename ForwardParamT<P2>::Type a2,
- typename ForwardParamT<P3>::Type a3,
- typename ForwardParamT<P4>::Type a4,
- typename ForwardParamT<P5>::Type a5){ //5个参数的情况
- return fptr(a1,a2,a3,a4,a5);
- }
- };
- /*
- * func_ptr(*fp):包装函数,用于方便地创建封装了函数指针的函数对象
- */
- template<typename RT>
- inline FunctionPtr<RT> func_ptr(RT (*fp)()){ //0个参数的情况
- return FunctionPtr<RT>(fp);
- }
- template<typename RT, typename P1>
- inline FunctionPtr<RT,P1> func_ptr(RT (*fp)(P1)){ //1个参数的情况
- return FunctionPtr<RT,P1>(fp);
- }
- template<typename RT, typename P1, typename P2>
- inline FunctionPtr<RT,P1,P2> func_ptr(RT (*fp)(P1,P2)){ //2个参数的情况
- return FunctionPtr<RT,P1,P2>(fp);
- }
- template<typename RT, typename P1, typename P2, typename P3>
- inline FunctionPtr<RT,P1,P2,P3> func_ptr(RT (*fp)(P1,P2,P3)){ //3个参数的情况
- return FunctionPtr<RT,P1,P2,P3>(fp);
- }
- template<typename RT, typename P1, typename P2, typename P3,typename P4>
- inline FunctionPtr<RT,P1,P2,P3,P4> func_ptr(RT (*fp)(P1,P2,P3,P4)){ //4个参数的情况
- return FunctionPtr<RT,P1,P2,P3,P4>(fp);
- }
- template<typename RT, typename P1, typename P2, typename P3,typename P4,typename P5>
- inline FunctionPtr<RT,P1,P2,P3,P4,P5> func_ptr(RT (*fp)(P1,P2,P3,P4,P5)){ //5个参数的情况
- return FunctionPtr<RT,P1,P2,P3,P4,P5>(fp);
- }
- #endif
测试代码如下:
- //functionptrtest.cpp:对封装函数指针的仿函数进行测试
- #include <iostream>
- #include <string>
- #include <typeinfo>
- #include "functionptr.hpp"
- double seven(){ //0个参数的情况
- return 7.0;
- }
- std::string more(){ //0个参数的情况
- return std::string("more");
- }
- int display(int a,int b){ //2个参数的情况
- std::cout<<a<<","<<b<<std::endl;
- return 0;
- }
- int output(int a,double b,std::string c){ //3个参数的情况
- std::cout<<a<<","<<b<<","<<c<<std::endl;
- return 0;
- }
- template <typename FunctorT>
- void demo(FunctorT func){ //func是封装了函数指针的函数对象,这个函数指针有0个参数
- std::cout << "Functor returns type: "
- << typeid(typename FunctorT::ReturnT).name() << '/n'
- << "Functor returns value: "
- << func() << '/n';
- }
- int main(){
- demo(func_ptr(seven));
- demo(func_ptr(more));
- func_ptr(display)(123,456);
- func_ptr(output)(83,3.14159,"Jack Zhou");
- return 0;
- }
3、仿函数的组合: Composer<FO1,FO2>用来组合两个仿函数的使用,先调用仿函数FO1(其参数个数可以是0-20个),再把其返回的结果的用作实参来调用仿函数FO2。可见FO2只能有一个参数,且与FO1的返回类型相同,这样才能组合使用。包装函数compose(f1,f2)用来方便地创建两个仿函数的组合对象。
- //composer.hpp:Composer<FO1,FO2>用于组合两个仿函数的使用
- #ifndef COMPOSER_HPP
- #define COMPOSER_HPP
- #include "forwardparam.hpp" //用于决定函数的形参T是传引用还是传原来的普通类型
- #include "functorparam.hpp" //用于获取仿函数各个参数的类型
- /*
- * BaseMem<C,N>:为了避免Composer多次继承同一个类而引入的间接层
- */
- template <typename C, int N>
- class BaseMem : public C { //直接继承模板参数C(是一个仿函数类)
- public:
- BaseMem(C& c) : C(c) { }
- BaseMem(C const& c) : C(c) { }
- };
- /*
- * Composer<FO1,FO2>:组合两个仿函数FO1、FO2,先调用FO1,再调用FO2
- * 通过BaseMem间接继承FO1和FO2,这样当FO1或FO2为空类是就可以进行空基类优化
- */
- template <typename FO1, typename FO2>
- class Composer : private BaseMem<FO1,1>,
- private BaseMem<FO2,2>{
- public:
- enum { NumParams = FO1::NumParams }; //参数个数
- typedef typename FO2::ReturnT ReturnT; //最后的返回类型
- //用FunctorParam来获取仿函数FO1的各个参数类型,
- //作为Composer的各个参数Param1T, Param2T,...
- // 用宏来简化参数类型的定义
- #define ComposeParamT(N) /
- typedef typename FunctorParam<FO1, N>::Type Param##N##T;
- //假设N到20为止,很少有多于20个参数的仿函数
- ComposeParamT(1)
- ComposeParamT(2)
- ComposeParamT(3)
- //...
- ComposeParamT(20)
- #undef ComposeParamT
- //构造函数:组合两个仿函数对象,这里考虑了各种const和非const的情况
- //把两个仿函数对象存储在基类子对象中,这就允许进行空基类优化
- Composer(FO1 const& f1, FO2 const& f2)
- : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) {
- }
- Composer(FO1 const& f1, FO2& f2)
- : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) {
- }
- Composer(FO1& f1, FO2 const& f2)
- : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) {
- }
- Composer(FO1& f1, FO2& f2)
- : BaseMem<FO1,1>(f1), BaseMem<FO2,2>(f2) {
- }
- //针对0个参数的函数调用
- ReturnT operator() (){ //针对0个参数的函数调用
- return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()());
- }
- //针对1个参数的函数调用
- ReturnT operator() (typename ForwardParamT<Param1T>::Type v1) {
- return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()(v1));
- }
- //针对2个参数的函数调用
- ReturnT operator() (typename ForwardParamT<Param1T>::Type v1,
- typename ForwardParamT<Param2T>::Type v2) {
- return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()(v1, v2));
- }
- //针对3个参数的函数调用
- ReturnT operator() (typename ForwardParamT<Param1T>::Type v1,
- typename ForwardParamT<Param2T>::Type v2,
- typename ForwardParamT<Param3T>::Type v3) {
- return BaseMem<FO2,2>::operator()(BaseMem<FO1,1>::operator()(v1, v2,v3));
- }
- //...
- //一直写到针对20个参数的函数调用
- };
- /*
- * compose(f1,f2)包装函数:用于方便地创建两个仿函数的组合
- */
- template <typename FO1, typename FO2>
- inline Composer<FO1,FO2> compose(FO1 f1, FO2 f2) {
- return Composer<FO1,FO2> (f1, f2);
- }
- #endif
- //composertest.cpp:对仿函数组合的测试
- #include <iostream>
- #include "composer.hpp"
- #include "functionptr.hpp"
- double add(double a, double b){
- return a+b;
- }
- double twice(double a){
- return 2*a;
- }
- int main(){
- std::cout << "compute (20+7)*2: "
- << compose(func_ptr(add),func_ptr(twice))(20,7)
- << '/n';
- return 0;
- }
解释:
(1)这里让Composer<FO1,FO2>通过BaseMem间接继承模板参数FO1,FO2,以便当FO1和FO2为空类时可以进行空基类优化。如果Composer直接继承FO1和FO2,当FO1和FO2为同一个类型时,编译会出错,因此我们需要要引入间接层BaseMem,现在的基类BaseMem<FO1,1>和BaseMem<FO2,2>并不是同一个类型。
(2)Composer是仿函数类,需要遵循仿函数的编写约定。它的各个参数类型是FO1的各个参数类型。因此我们需要用FunctorParam来抽取仿函数FO1的各个参数类型,以作为Composer的各个参数Param1T, Param2T,...,Param20T。
(3)构造函数用于组合两个仿函数对象,它需要考虑两个仿函数的const和非const的情况。下面的函数调用运算符中是先调用FO1,把返回结果作为实参再调用FO2。
4、仿函数参数的值绑定: 即将仿函数的某个参数绑定为一个特定的值,这样仿函数的参数就可以减少一个。我们需要开发出一个绑定器(是一个仿函数)来作绑定操作,一个独立的类来存放绑定值。绑定器的各个参数类型萃取也比较麻烦,它需要去掉仿函数中被绑定的参数,然后后面的参数又要前移一个位置,因此我们把绑定器的参数类型萃取抽离出来设计成一个独立的模板。
下面的BoundVal<T>用于在运行期存放T类型的绑定值(这个值后面会被绑定到仿函数的某个参数上去),而StaticBoundVal<T> 则用于在编译期存放T类型的绑定值。
- //boundval.hpp:存放绑定值的模板,可通过get()来获取这个绑定值
- #ifndef BOUND_VAL_HPP
- #define BOUND_VAL_HPP
- #include "typeop.hpp"
- template <typename T>
- class BoundVal{ //在运行期存放T类型的绑定值
- private:
- T value; //存放绑定值的对象
- public:
- typedef T ValueT;
- BoundVal(T v):value(v){ //把要绑定的值通过构造函数传进来
- }
- typename TypeOp<T>::RefT get(){ //设置或获取绑定的值
- return value; //返回非const的引用
- }
- };
- template <typename T, T Val>
- class StaticBoundVal{ //在编译期存放T类型的绑定值
- public:
- typedef T ValueT;
- T get(){ //通过get()来获取绑定的值
- return Val;
- }
- };
- #endif
下面的BinderParams<F,P>用来萃取绑定器的参数类型。它只要去掉仿函数中被绑定的参数, 并把后面的参数前移1个位置即可。
- //binderparams.hpp:绑定器的参数类型列表,去掉仿函数中被绑定的参数,
- //并把后面的参数前移1个位置即可
- #ifndef BINDER_PARAMS_HPP
- #define BINDER_PARAMS_HPP
- #include "ifthenelse.hpp"
- /*
- * BinderParams<F,P>:用于将被绑定参数(即第P个参数)后面的参数前移1个位置
- * BinderParams<F,P>::ParamNT:仿函数F被绑定后的第N个参数类型
- */
- template<typename F, int P>
- class BinderParams{
- public:
- enum { NumParams = F::NumParams-1 };//一个参数已绑定,故参数个数减1
- //将被绑定参数(即第P个参数)后面的参数前移1个位置
- #define ComposeParamT(N) /
- typedef typename IfThenElse<(N<P), FunctorParam<F, N>, /
- FunctorParam<F, N+1> /
- >::ResultT::Type /
- Param##N##T;
- //假设N到20为止,很少有多于20个参数的仿函数
- ComposeParamT(1)
- ComposeParamT(2)
- ComposeParamT(3)
- //...
- ComposeParamT(20)
- #undef ComposeParamT
- };
- #endif
下面的SignSelectT<S,NegT,ZeroT,PosT>根据整数S是正、0、还是负来选择相应的类型。
- //signselect.hpp:根据整数S是正、0、还是负来选择相应的类型
- #ifndef SIGN_SELECT_HPP
- #define SIGN_SELECT_HPP
- #include "ifthenelse.hpp"
- template <int S, typename NegT, typename ZeroT, typename PosT>
- struct SignSelectT{
- typedef typename
- IfThenElse<(S<0),
- NegT,
- typename IfThenElse<(S>0),
- PosT,
- ZeroT
- >::ResultT
- >::ResultT
- ResultT;
- };
- #endif
下面是真正的绑定器Binder<FO,P,V>。它把仿函数FO的第P个参数绑定为一个存放在V类型对象中的绑定值,绑定后它还要完成对仿函数FO的调用,即Binder<FO,P,V>本身就相当于被绑定后的FO。Binder的参数类型列表Params是仿函数FO去掉绑定参数后的各个参数类型,可见Binder::operator()的参数个数比底层的FO::operator()少一个,因此它在调用底层的FO::operator()时需要重新确定原来FO的参数,并指定一个参数为绑定值。嵌套类模板ArgSelect<A>就是用来确定FO的各个实参。包装函数bind<P>(fo,val)用于方便地将仿函数对象fo的第P个参数绑定为值val。
- //binder.hpp:用于对仿函数的参数进行值绑定
- #ifndef BINDER_HPP
- #define BINDER_HPP
- #include "ifthenelse.hpp"
- #include "typeop.hpp"
- #include "boundval.hpp" //用于存放要绑定的值
- #include "forwardparam.hpp" //用于决定仿函数的形参T是传引用还是传原来的普通类型
- #include "functorparam.hpp" //获取仿函数各个参数的类
- #include "binderparams.hpp" //用于将被绑定参数后面的参数前移1个位置
- #include "signselect.hpp" //根据整数的正负性来选择相应的类型
- /*
- * Binder<FO,P,V>:把仿函数FO的第P个参数绑定为一个存放在V类型对象中的绑定值,
- * V是BoundVal<T>或StaticBoundVal<T,val>类型,里面存放了真正
- * 的绑定值(是T类型的)
- */
- template <typename FO, int P, typename V>
- class Binder : private FO, private V{
- public:
- enum { NumParams = FO::NumParams-1 }; //一个参数已经绑定,故参数个数减1
- typedef typename FO::ReturnT ReturnT; //返回类型
- typedef BinderParams<FO, P> Params; //参数类型列表:Params中含有仿函数FO
- //去掉绑定参数后的各个参数类型
- //定义Binder需要的各个参数类型(或者为原来类型的const引用,或者为原来的普通类型)
- #define ComposeParamT(N) /
- typedef typename ForwardParamT<typename Params::Param##N##T>::Type /
- Param##N##T;
- ComposeParamT(1)
- ComposeParamT(2)
- ComposeParamT(3)
- //...
- ComposeParamT(20)
- #undef ComposeParamT
- //构造函数:根据传过来的仿函数对象和存放了绑定值的V类型对象构造Binder,
- //这里考虑了各种const和非const的情况
- Binder(FO& f): FO(f) {}
- Binder(FO& f, V& v): FO(f), V(v) {}
- Binder(FO& f, V const& v): FO(f), V(v) {}
- Binder(FO const& f): FO(f) {}
- Binder(FO const& f, V& v): FO(f), V(v) {}
- Binder(FO const& f, V const& v): FO(f), V(v) {}
- //构造函数模板:直接根据仿函数对象和要绑定的值(T类型的)构造Binder,绑定的值存储在
- //BoundVal<T>中,即在运行期存储绑定值
- template<class T>
- Binder(FO& f, T& v): FO(f), V(BoundVal<T>(v)) {}
- template<class T>
- Binder(FO& f, T const& v): FO(f), V(BoundVal<T const>(v)) {}
- //函数调用:对调用实参个数不同的情况,都要重载调用运算符
- //Binder::operator()的参数个数比底层的FO::operator()少一个
- ReturnT operator()(){
- return FO::operator()(V::get());
- }
- ReturnT operator()(Param1T v1){
- //根据实参位置(1和2)与P的相对位置,用from函数自动选择是使用用户
- //提供的v1,还是使用被绑定的值V::get()
- return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()),
- ArgSelect<2>::from(v1,v1,V::get()));
- }
- ReturnT operator()(Param1T v1, Param2T v2){
- return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()),
- ArgSelect<2>::from(v1,v2,V::get()),
- ArgSelect<3>::from(v2,v2,V::get()));
- }
- ReturnT operator()(Param1T v1, Param2T v2, Param3T v3) {
- return FO::operator()(ArgSelect<1>::from(v1,v1,V::get()),
- ArgSelect<2>::from(v1,v2,V::get()),
- ArgSelect<3>::from(v2,v3,V::get()),
- ArgSelect<4>::from(v3,v3,V::get()));
- }
- //...
- //一直写到针对20个参数的函数调用
- private:
- template<int A>
- class ArgSelect{ //私有的嵌套类模板:实参选择器,用来确定FO原来的各个实参
- public:
- //定义绑定实参前面的类型:FO的第A个参数类型ArgSelect<A>::NoSkipT恰为
- //Binder的第A个参数类型(取其引用类型),当A达到比NumParams多1时,退回到Binder的A-1处
- typedef typename TypeOp<
- typename IfThenElse<(A<=Params::NumParams),
- FunctorParam<Params, A>,
- FunctorParam<Params, A-1>
- >::ResultT::Type>::RefT
- NoSkipT;
- //定义绑定实参后面的类型:FO的第A个参数类型ArgSelect<A>::SkipT恰为
- //Binder的第A-1个参数类型(取其引用类型),当A为1时,前移到Bindder的A处
- //ArgSelect<A>::NoSkipT
- typedef typename TypeOp<
- typename IfThenElse<(A>1),
- FunctorParam<Params, A-1>,
- FunctorParam<Params, A>
- >::ResultT::Type>::RefT
- SkipT;
- //定义绑定实参处的类型:在存放绑定值的V类型中有记录
- typedef typename TypeOp<typename V::ValueT>::RefT BindT;
- //3个不同的嵌套类,用来实现3种情况下FO实参值的选择
- class NoSkip{ //选择绑定参数前面的实参:为用户指定的实参
- public:
- static NoSkipT select(SkipT prev_arg, NoSkipT arg,
- BindT bound_val) {
- return arg;
- }
- };
- class Skip{ //选择绑定参数后面的实参:为用户指定的实参
- public:
- static SkipT select(SkipT prev_arg, NoSkipT arg,
- BindT bound_val){
- return prev_arg;
- }
- };
- class Bind{ //选择绑定参数处的实参:为绑定值
- public:
- static BindT select(SkipT prev_arg, NoSkipT arg,
- BindT bound_val){
- return bound_val;
- }
- };
- //根据A与P的相对位置返回仿函数FO中A处的参数类型
- typedef typename SignSelectT<A-P, NoSkipT,
- BindT, SkipT>::ResultT
- ReturnT;
- //根据A与P的相对位置返回仿函数FO的实参选择类(上面的3个嵌套类之一,用来
- //选择FO在A处的实参值)
- typedef typename SignSelectT<A-P, NoSkip,
- Bind, Skip>::ResultT
- SelectedT;
- //实际的选择函数:
- static ReturnT from (SkipT prev_arg, NoSkipT arg,
- BindT bound_val){
- //根据获得的选择类,调用select来选择相应的实参值
- return SelectedT::select(prev_arg, arg, bound_val);
- }
- };
- };
- /*
- * 包装函数bind<P>(fo,val):将仿函数对象fo的第P个参数绑定为值val
- * FO可演绎出来,FunctorParam<FO,P>::Type为绑定值的类型
- * BoundVal<typename FunctorParam<FO,P>::Type>为存放绑定值的V类型
- * ForwardParamT可用来决定val是传引用还是传原来的类型
- */
- template <int P,typename FO> //P为绑定参数的位置,FO为仿函数
- inline Binder<FO,P,BoundVal<typename FunctorParam<FO,P>::Type> >
- bind(FO const& fo,
- typename ForwardParamT<typename FunctorParam<FO,P>::Type>::Type val){
- //返回一个对fo进行绑定后的Binder仿函数对象
- return Binder<FO,
- P,
- BoundVal<typename FunctorParam<FO,P>::Type>
- >(fo,BoundVal<typename FunctorParam<FO,P>::Type>(val));
- }
- #endif
解释:
(1)Binder继承自模板参数FO和V,这样就可以使用空基类优化技术。Binder是一个仿函数,因此它要遵循仿函数的编写约定,定义参数个数和各个参数类型(用上面的BinderParams来萃取)。
(2)构造函数根据传过来的仿函数对象和存放了绑定值的V类型对象构造Binder,它需要考虑各种const和非const的情况。而构造函数模板则直接根据仿函数对象和要绑定的值(T类型的)构造Binder,绑定的值存储在BoundVal<T>中,即在运行期存储绑定值。
(3)ArgSelect<A>用来确定原来FO的各个实参,这样才能在Binder中完成对被绑定的FO的调用。它先根据A的位置定义绑定实参前面的类型NoSkipT、绑定实参后面的类型SkipT、以及绑定实参处的类型BindT。然后定义3个不同的嵌套类,里面有一个静态的select函数,用来实现3种情况下对FO实参值的选择。接着根据A与P的相对位置,用SignSelectT得到仿函数FO中A处的参数类型、以及仿函数FO的实参选择类(即上面的的3个嵌套类之一)。最后是真正的实参选择函数from,它调用相应选择类的select,根据实参位置A与P的相对位置,自动选择FO中A处是使用用户提供的实参,还是使用被绑定的值。
(4)函数调用Binder::operator()对调用实参个数不同的情况,都要进行重载。Binder::operator()直接调用FO::operator(),而各个实参是根据所在位置用ArgSelect<n>::from来自动选择,对FO中绑定处的参数用绑值V::get(),对其他位置的参数则用用户传递过来的实参。
(5)包装函数bind<P>(fo,val)直接返回一个对fo绑定后的少了一个参数的仿函数对象。它的使用就相当于对少了一个参数的fo的使用。
测试代码如下:
- //bindertest.cpp:对绑定器Binder的测试
- #include <string>
- #include <iostream>
- #include "functionptr.hpp"
- #include "binder.hpp"
- //这个函数会被func_ptr封装为仿函数
- bool func(std::string const& str, double d, float f){
- std::cout << str << ": "
- << d << (d<f? "<": ">=")
- << f << '/n';
- return d<f;
- }
- int main(){
- //把仿函数func_ptr(func)的第1个参数绑定为"Comparing"
- bool result = bind<1>(func_ptr(func), "Comparing")(1.0, 2.0);
- std::cout << "bound function returned " << result << '/n';
- return 0;
- }
5、普通函数参数的值绑定: 可先用上面的func_ptr(*fp)把函数(最多只能接受5个参数)封装成仿函数,再用bind来进行值绑定即可。上面的测试代码中就是这样用的。从中我们可以看出,通过使用func_ptr和bind的实现,我们可以对任意普通函数的参数进行值绑定。
============================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4550898
我们知道构造函数和非成员函数函数不可能是虚函数,但有时候当我们使用一个继承体系时,又希望这些函数具有多态行为,能根据基类指针(或引用)自动地使用子类的实现。其实,通过一些设计技术,我们可以达到这样的目的。
1、虚构造函数: 完成对象的创建,并返回基类指针的函数。它的功能就是创建对象,但具有了多态行为,这种虚构造函数有很多的应用。
设想写一个时事通讯(newsletter)的程序,组成NewsLetter的元素是文本TextBlock和图片Graphic,所有元素都从抽象基类NLComponent派生。图片和文本的内容在硬盘(或网络)上,这样TextBlock和Graphic的构造函数需要根据输入流来构造对象。代码如下:
- //newsletter1.hpp:处理时事通讯的程序,时事通讯包含的组件有文本和图片
- //有多态化的构造函数
- #ifndef NEWSLETTER_HPP
- #define NEWSLETTER_HPP
- #include <list>
- #include <iostream>
- class NLComponent{ //抽象基类
- public:
- //至少有一纯虚函数
- };
- class TextBlock : public NLComponent{ //文本
- public:
- TextBlock(std::istream& in){
- //用in中的文本数据初始化各个成员
- }
- //...
- }
- };
- class Graphic : public NLComponent{ //图片
- public:
- Graphic(std::istream& in){
- //用in中的图片数据初始化各个成员
- }
- //...
- };
- inline NLComponent* constructComponent(std::istream& in){//多态化的构造函数
- //用一个变量data保存in中的数据
- if(/* str是文本数据 */)
- return new TextBlock(in);
- if(/* str是文本数据 */)
- return new Graphic(in);
- //...
- //默认情况返回文本数据
- return new TextBlock(in);
- }
- class NewsLetter{ //时事通讯
- private:
- std::list<NLComponent*> components; //存储了各个文本或图片
- public:
- NewsLetter(std::istream& in);
- //...
- };
- NewsLetter::NewsLetter(std::istream& in){ //多态化的构造函数
- while(in){ //不断地从流中读数据
- //用虚构造函数构造对象,然后加入到components列表中
- components.push_back(construtComponent(in));
- }
- }
- #endif
这里的constructComponent就是所谓的虚构造函数。我们在NewLetter中需要创建所有的数据成员(各个元素对象),并把指针压入components列表中。但components列表中只需压入NLComponent*型的指针,无需知道这个指针所指对象的确切子类型,这就需要各个子类的构造函数具有多态行为,希望它们能返回基类指针。但我们知道类的构造函数不能为虚函数,因此我们把对象的创建工作封装成constructComponent函数,它根据所读取的不同数据来创建不同的子类对象,并返回一个NLComponent*指针。为避免封装带来的效率损失,我们把它声明为inline。constructComponent中一般有一个分支语句,根据所读取数据的不同,创建相应的对象。constructComponent充当了真正构造函数的多态版本,它具有多态行为,因此可称为虚构造函数。这里constructComponent是全局函数,我们也可以把它实现为NewsLetter类的静态成员函数。如果我们不想动态分配资源,则在constructComponent中不用new,返回时用基类引用即可。
虚构造函数的实现思想: 在继承体系中定义一个全局函数,它用来根据一个标志构造相应的子类对象,并返回指向它的基类指针。这样这个全局函数相当于一个构造函数,又具有了多态行为。
2、虚拷贝构造函数: 当我们要对基类指针所指对象进行拷贝时,可能不知道指针所指向的到底是那个子类对象,因此无法调用子类的拷贝构造函数。这就需要在继承体系的基类中指定一个统一的接口(一般是名为clone的虚函数),用来实现拷贝功能。每个子类重写clone(),它直接调用实际的拷贝构造函数拷贝*this,返回这个副本的指针。子类中的clone()虽然返回本子类的指针,但根据C++标准,它仍然是重写了基类的clone版本(基类中的版本返回基类指针)。clone()就相当于虚拷贝构造函数,它具有多态行为,因为可以用基类指针来指向这个返回的副本。
例如对于NewsLetter的拷贝,我们需要拷贝components列表,由于里面存放的是NLComponent*指针,因此要作深拷贝。即我们要把指针所指的各个对象拷贝过来,这需要调用对象的拷贝构造函数,但我们不并知道对象的具体子类型,因此需要在继承体系中提供clone()函数来实现这个功能。
- //newsletter2.hpp:处理时事通讯的类,时事通讯包含的组件有文本和图片
- //有多态化的拷贝构造函数
- #ifndef NEWSLETTER_HPP
- #define NEWSLETTER_HPP
- #include <list>
- class NLComponent{ //抽象基类
- public:
- virtual NLComponent* clone() const=0;
- //...
- };
- class TextBlock : public NLComponent{
- public:
- virtual TextBlock* clone() const{ //多态化的拷贝构造函数
- return new TextBlock(*this);
- }
- //...
- };
- class Graphic : public NLComponent{
- public:
- virtual Graphic* clone() const{
- return new Graphic(*this);
- }
- //...
- };
- class NewsLetter{
- private:
- std::list<NLComponent*> components;
- public:
- NewsLetter(){ }
- NewsLetter(NewsLetter const& rhs);
- //...
- };
- NewsLetter::NewsLetter(NewsLetter const& rhs){ //NewsLetter的拷贝构造函数
- //注意访问控制是针对类与类之间的,而不是针对同类的不同对象
- //因此rhs的components虽为private,但components是NewsLetter类中的成员,
- //现在已经在NewLetter的作用域中,因此可以访问
- for(std::list<NLComponent*>::const_iterator it=rhs.components.begin();
- it!=rhs.components.end();++it){
- components.push_back((*it)->clone()); //使用了虚的拷贝构造函数
- }
- }
- #endif
- //newsletter2test.cpp:对多态化的拷贝构造函数的测试
- #include "newsletter2.hpp"
- #include <iostream>
- int main(){
- NewsLetter news;
- NewsLetter let(news); //调用NewsLetter的拷贝构造函数
- std::cout<<"Yes"<<'/n';
- return 0;
- }
在继承体系中,我们添加了一个clone()虚函数,各个子类都要实现clone()函数。现在,即使指针不知道所指向的到底是那个子类对象,但它知道所有的子类都有一个clone()函数,用它可以完成拷贝功能,因此只要调用clone()就可以了,clone()相当于拷贝构造函数的多态版本,同时它又是内联的,基本上没有性能损失。事实上,在Java、C#这样的语言中就使用了这种技术(它们的类继承体系中有一个clone函数)。
思想总结: 从中我们可以看出,多态机制最大的好处就是让我们可以用统一的标记(基类指针或引用)来关联不同的行为(各个子类中重写的虚函数),但它也会产生副作用。它隐藏了指针(或引用)所指对象的类型,使得我们有时候不能对所指对象进行一些具体的操作。这就需要我们在继承体系的基类中指定一个统一的接口,各个子类必须重写这个接口,完成我们需要的具体操作。有了这个契约,即使我们不知道对象的具体类型,但我们知道每个类都有一个统一的接口来完成相应的操作。
多态还有一些其他的副作用。比如把多态应用于数组时,就会出问题。如果我们用基类指针(或引用)来指向一个包含派生类对象的数组,编译器就会认为数组中的每个对象的大小都和基类一样,在对数组进行遍历时,指针的移动(array[i]相当于*(array+i),对指针进行运算)会导致获得的对象地址不准确。另外,在用delete[]删除数组也同样有这样的问题,因为delete[]也要遍历数组的各个元素来调用它们的析构函数,这同样通过作指针移动运算来计算偏移地址。一般地,多态和指针运算就是不能用在一起,在数组中最好不要使用多态。要避免这个问题,我们在做设计时应尽量少从具体类派生出具体类,而让一个类派生自抽象基类,这样我们就不太可能犯这种把多态应用于数组的错误。另一种方法是少用或不用数组,多用std::vector、std::list等容器。当我们需要数组多态时,使用容器来存放基类指针,这还能让它们指向不同子类的对象。在上面的实现中,我们就使用了std::list来存放NLComponent*型指针,这样就可以实现安全的多态行为。
3、虚的非成员函数: 非成员函数不可能是虚函数,但根据函数的参数(是某个基类指针或引用),可以让非成员函数具有多态行为。只要在参数类型的各个子类中用统一的虚函数来完成实际任务,然后让非成员函数调用这个虚函数即可。由于虚函数是多态的,这就导致非成员函数也有了多态行为。
比如我们希望时事通讯的各个元素类具有输出功能,能够输出文本或图片的内容。显而易见的做法是让它们重载输出运算符<<,只要在抽象基类NLComponent中声明operator<<这个统一的虚接口,所有子类就必须实现它。在应用时只要写成"cout<<基类引用",就可以自动调用相应子类的operator<<,输出相应的内容。但是我们知道operator<<通常并不把它重载为成员函数,如果要重载为成员函数,就只能写成“ostream& operator<<(ostream& out)”的形式,使用的形式是“t<<cout“,t为TextBlock或Graphic对象,这与传统使用习惯正好相反。因此operator<<一般重载为非成员函数,但又要让它具有多态行为,怎么办呢?我们可以在继承体系中实现一个统一的虚函数接口用于打印输出,然后让operator<<非成员函数调用它即可。
- //newsletter3.hpp:处理时事通讯的类,有多态化的非成员函数
- #ifndef NEWSLETTER_HPP
- #define NEWSLETTER_HPP
- #include <iostream>
- #include <list>
- class NLComponent{ //抽象基类
- public:
- //用于打印输出
- virtual std::ostream& print(std::ostream& out) const=0;
- };
- class TextBlock : public NLComponent{
- public:
- virtual std::ostream& print(std::ostream& out) const{
- //输出文本内容
- }
- virtual TextBlock* clone() const{ //多态化的拷贝构造函数
- return new TextBlock(*this);
- }
- //...
- };
- class Graphic : public NLComponent{
- public:
- virtual std::ostream& print(std::ostream& out) const{
- //输出图片内容
- }
- virtual Graphic* clone() const{
- return new Graphic(*this);
- }
- //...
- };
- class NewsLetter{
- private:
- std::list<NLComponent*> components;
- public:
- NewsLetter(){ }
- //...
- };
- //非成员函数:具有虚函数的行为
- inline std:ostream& operator<<(std::ostream& out,NLComponent const& c){
- return c.print(out); //自动绑定到子类的print上
- }
- #endif
这里的统一接口print函数完成实际的打印输出功能,它其实就相当于成员函数版的operator<<,只不过函数名改成了print。非成员函数operator<<直接根据基类引用调用print,这就获得了多态行为。
==================================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4555847
1、允许一个或零个对象: 把所有的构造函数声明为私有,则不能创建任何对象。如果允许只创建一个对象,可用一个全局函数(或静态成员函数)来创建唯一的一个静态对象,并返回其引用,为提高效率,可把全局函数声明为inline。注意这个全局函数要声明为类的友元函数,因为要使用私有的构造函数。
例如我们想创建一个打印机对象,但希望实际上只有一个打印机,可以这样写:
- //printer.hpp:只允许创建一个打印机对象,即单例模式
- #ifndef PRINTER_HPP
- #define PRINTER_HPP
- class PrintJob{
- public:
- PrintJob(const string& whatToPrint){
- //...
- }
- //...
- };
- class Printer{ //打印机类
- private:
- //所有构造函数都声明为私有,则不能作为基类使用
- Printer(){
- //...
- }
- Printer(Printer const& rhs){
- //...
- }
- friend Printer& thePrinter();
- //...
- public:
- void submitJob(PrintJob const& job){
- //...
- }
- void reset(){
- //...
- }
- void performSelfTest(){
- //...
- }
- //...
- };
- inline Printer& thePrinter(){ //获取唯一的一个打印机对象
- static Printer p;
- return p;
- }
- //...
- #endif
这里我们把thePrinter()实现为一个全局函数,里面只创建一个静态对象,返回其引用,这样客户端代码中的所有thePrinter()调用都只使用这个唯一的静态对象。注意,对构造函数,可以只声明为私有,如果不使用它,则可以不提供定义,但对需要被使用的构造函数(因为是私有,只能在类的成员函数或友元函数中使用)则必须提供定义。当然,我们也可以把thePrinter()实现为Printer类的静态成员函数,这样就不需要友元声明了。但使用全局函数也有优势,因为在某些类中声明的静态对象即使没用到也会被构造(以及析构)出来,而全局函数里的静态对象只有执行函数时才被创建。
另一方面,我们有时候会把那个唯一的p对象声明为全局的静态对象或Printer类的静态成员,然后在thePrinter()或Printer::thePrinter()中直接返回它,这不是一种好的实现方案。我们称函数内的static对象称为local static对象,其他的static对象(class作用域、namespace作用域、global作用域、file作用域)称为non-local static对象。C++只保证在一个特定编译单元内的静态对象的初始化顺序,对不同编译单元内的non-local static对象的初始化次序没有明确定义。可见这样的实现方案会使得p对象的初始顺序不明确。
当我们需要用到non-local static对象,又要保证正确的初始化顺序时,解决方法恰恰就是将non-local static对象放在一个inline包装函数中,函数返回一个引用指向此对象(变成local static对象)。用函数调用来替换直接访问non-local static对象,这时函数内的局部静态对象就会有确定的初始化顺序(在第一次执行函数时初始化)。注意,我们通常在设计时并不建议返回指向函数内局部对象的reference,但这里的包装函数行为单纯,只是包装一个static对象并返回其reference,并不做其他工作。当多次调用包装函数时,reference指向同一个static对象,并且值均相同(包装函数内并没有改变对象的成员值),因此这样实现没有副作用。
注意,构造函数声明为私有的类不能作为基类使用,也不能组合到其他类中来使用。我们可以放宽一点,把构造函数声明为protected的,这样类同样不可以创建对象,但可以被继承。
2、允许创建任意数量的对象,但不允许作为基类: 把构造函数声明为私有,这样就不允许作为基类了。同时我们提供相应的伪构造函数,用它来创建对象并返回。例如对有限状态机类,如下:
- //fsa.hpp:有限状态机类,允许创建任意数量的对象,但不允许作为基类
- #ifndef FSA_HPP
- #define FSA_HPP
- class FSA{ //有限状态机类
- private:
- //构造函数声明为私有
- FSA(){
- //...
- }
- FSA(FSA const& rhs){
- //...
- }
- public:
- static FSA* makeFSA(){ //伪构造函数
- return new FSA();
- }
- static FSA* makeFSA(FSA const& rhs){ //伪拷贝构造函数
- return new FSA(rhs);
- }
- //...
- };
- #endif
客户端使用这个类时,就必须调用伪构造函数来创建对象。当然,由于对象是动态分配的,这时客户必须自己调用delete来删除对象。
3、允许对象个数限制在某个给定的值: 使用前面在“模板与继承相结合的威力”中介绍的奇异递归模板模式技术,编写一个具有实例计数功能的基类即可,让需要计数的类继承这个类。因为类要实现计数功能,是一种“实现”的关系,故最好用私有继承,表示用这个基类来实现需要的功能。这里我们做一下修改,当需要计数的类对象个数超过上限时就抛出异常。注意不同的类可以通过特化这个基类的表示上限值的成员,以满足不同类对象个数的要求。
- //objectcounter.hpp:对对象创建进行记数的模板
- #ifndef OBJECT_COUNTER_HPP
- #define OBJECT_COUNTER_HPP
- #include <cstddef>
- template<typename T>
- class ObjectCounter{ //用来记录T型对象构造的总个数
- private:
- static std::size_t numObjects;//存在对象的个数
- static const std::size_t maxObjects; //对象个数的上限,由需要计数的类来指定
- void incr(){
- if(ObjectCounter<T>::numObjects>=ObjectCounter<T>::maxObjects)
- throw TooManyObjects();
- ++ObjectCounter<T>::numObjects;
- }
- protected:
- ObjectCounter(){ //缺省构造函数
- incr();
- }
- ObjectCounter(ObjectCounter<T> const& rhs){ //拷贝构造函数
- incr();
- }
- ~ObjectCounter(){ //析构函数
- --ObjectCounter<T>::numObjects;
- }
- public:
- class TooManyObjects{ }; //异常类
- static std::size_t objectCount(){ //返回当前对象个数
- return ObjectCounter<T>::numObjects;
- }
- };
- //static成员变量必须在类外进行定义,由于是非const的,因此也要类外初始化
- template<typename T>
- std::size_t ObjectCounter<T>::numObjects=0;
- #endif
- //mystring.hpp:使用了对象计数的MyString类
- #ifndef MYSTRING_HPP
- #define MYSTRING_HPP
- #include "objectcounter.hpp"
- class MyString : private ObjectCounter<MyString>{
- public:
- //由于是私有继承,要用using把objectCount函数变成公有
- using ObjectCounter<MyString>::objectCount;
- //...
- };
- //特化ObjectCounter的maxObjects成员,值为3,以限制MyString类对象的个数,
- //这个语句必须加入到MyString类的实现文件中
- template<>
- const std::size_t ObjectCounter<MyString>::maxObjects=3;
- #endif
- //countprinter.hpp:使用了对象计数的Printer类
- #ifndef PRINTER_HPP
- #define PRINTER_HPP
- #include "objectcounter.hpp"
- class Printer : private ObjectCounter<Printer>{
- public:
- //由于是私有继承,要用using把objectCount函数变成公有
- using ObjectCounter<Printer>::objectCount;
- //...
- };
- //对Printer,对象个数特化为值4
- template<>
- const std::size_t ObjectCounter<Printer>::maxObjects=4;
- #endif
- //countertest.cpp:客户端代码,对对象计数进行测试
- #include <iostream>
- #include "mystring.hpp"
- #include "countprinter.hpp"
- int main(){
- MyString s1,s2;
- std::cout<<"number of MyString: "
- <<MyString::objectCount()<<std::endl;
- std::cout<<"number of MyString: "
- <<s1.objectCount()<<std::endl;
- s1.~MyString(); //减少一个对象
- std::cout<<"number of MyString: "
- <<MyString::objectCount()<<std::endl;
- try{
- MyString s3,s4,s5; //对象个数超过上限,会抛出出异常
- }catch(ObjectCounter<MyString>::TooManyObjects& e){
- std::cout<<"Too many MyString objects."<<std::endl;
- }
- Printer t1,t2,t3;
- std::cout<<"number of Printer: "
- <<Printer::objectCount()<<std::endl;
- std::cout<<"number of Printer: "
- <<t1.objectCount()<<std::endl;
- Printer t4,t5; //对象个数超过上限,会抛出出异常,终止程序
- return 0;
- }
这里ObjectCounter<T>模板用于计数,它需要作为基类,成员名的前面最好加类作用域符(也可用this->),使它变成依赖型名称。表示对象个数上限值的maxObjects成员是一个静态常量,它并没有初始化。我们必须在需要计数的类MyString、Printer中指定它的值。不同类的对象个数上限可能不同,因此我们只要特化maxObjects这个成员并指定需要的值即可(类模板可以只特化某个成员)。在MyString、Printer这些类中,由于是私有继承,因此要用using声明把objectCount函数变成公有,这样就可以用它来获取当前对象的个数。
=================================================================
转自 http://blog.csdn.net/zhoudaxia/article/details/4560338
1、要求对象分配在堆上: 栈上对象在定义时自动构造,在生存期结束时自动析构。因此可把析构函数声明为私有,这样栈上对象离开作用域时就会出错,不能自动析构。同时为了在堆上能够正确的创建和删除对象,提供一个伪析构函数来访问真正的析构函数。客户端使用时需要调用伪析构函数来销毁堆上的对象。
例如,对于一个表示无限精度数字的类,要让对象只能创建在堆上,如下:
- //upnumber1.hpp:表示无限精度数字的类,其对象只能创建在堆上
- #ifndef UPNUMBER_HPP
- #define UPNUMBER_HPP
- class UPNumber{
- public:
- UPNumber(){
- //...
- }
- UPNumber(int val){
- //...
- }
- UPNumber(double val){
- //...
- }
- UPNumber(UPNumber const& rhs){
- //...
- }
- void destroy() const{ //伪析构函数
- delete this;
- }
- //...
- private:
- ~UPNumber(); //析构函数私有
- };
- #endif
- //upnumber1test.cpp:对UPNumber,测试对象是否只能创建在堆上
- #include "upnumber1.hpp"
- int main(){
- UPNumber *p=new UPNumber;
- p->destroy(); //调用伪析构函数,不用直接用delete p;
- UPNumber n; //会报错,析构函数为私有,编译器不能创建栈对象(否则不能销毁)
- return 0;
- }
注意,析构函数声明为私有的类不能被继承,也不能被其他类包含。我们可以放宽一点,把析构函数声明为protected的,这样就可以被继承,包含这个类的其他类可以通过包含这个类对象的指针,而不是对象来达到组合的目的。
2、判断对象是否在堆上: 堆上的对象肯定通过new调用了operator new,而栈上的对象则没有调用它。因此可以给类增加一个标志变量flag来判断是否调用了operator new,类需要重写operator new来设置这个flag。同时增加一个公有的布尔变量onTheHeap以让客户端判断对象是否在堆上。在所有的构造函数中都需要根据flag是否被设置来初始化onTheHeap的值。
对上面的UPNumber类,可做如下的设计:
- //upnumber2.hpp:表示无限精度数字的类,可判断单个的对象或整个数组内存是否在堆上
- #ifndef UPNUMBER_HPP
- #define UPNUMBER_HPP
- #include <cstddef>
- #include <new>
- class UPNumber{
- private:
- static bool flag;
- void initHeapFlag(){
- if(!flag){ //如果没有调用operator new和operator new[]
- onTheHeap=false; //说明是栈对象
- }else{
- onTheHeap=true; //否则调用了operator new或operator new[],是堆对象
- flag=false; //重置标志位
- }
- }
- public:
- bool onTheHeap;
- static void* operator new(std::size_t size);
- static void* operator new[](std::size_t size) throw(std::bad_alloc);
- UPNumber(){
- initHeapFlag(); //初始化onTheHeap标志
- //...
- }
- UPNumber(int val){
- initHeapFlag();
- //...
- }
- UPNumber(double val){
- initHeapFlag();
- //...
- }
- UPNumber(UPNumber const& rhs){
- initHeapFlag();
- //...
- }
- //...
- };
- bool UPNumber::flag=false; //静态变量在类外仍然需要定义
- void* UPNumber::operator new(std::size_t size){
- flag=true; //设计标志位为true
- return ::operator new(size);
- }
- void* UPNumber::operator new[](std::size_t size) throw(std::bad_alloc){
- flag=true;
- return ::operator new[](size);
- }
- #endif
- //upnumber2test.cpp:对UPNumber的测试,判断对象是否在堆上
- #include "upnumber2.hpp"
- #include <iostream>
- int main(){
- UPNumber a; //在栈上
- std::cout<<a.onTheHeap<<std::endl;
- UPNumber *b=new UPNumber(); //在堆上
- std::cout<<b->onTheHeap<<std::endl;
- delete b;
- UPNumber *num=new UPNumber[10]; //在堆上
- std::cout<<num->onTheHeap<<std::endl; //相当于输出数组第1个元素是否在堆上
- std::cout<<"The UPNumber array's heap flag: "<<std::endl;
- for(int i=0;i<10;++i){
- //只有第1个对象会输出1表示在堆上,其余都会输出0,但实际上
- //它们是在堆上的
- std::cout<<num[i].onTheHeap<<std::endl;
- }
- return 0;
- }
解释:
(1)这样的设计只能判断单个的对象是否在堆上。我们还重写了operator new[],这使得它可以判断整个数组所占的内存是否在堆上,但它却不能判断数组中的每个对象是否在堆上。从测试代码的运行结果就可以看出,对堆上的数组中的每个对象,只有第1个输出为1(即true),其余全部输出为0(即false),但我们知道实际上各个对象都在堆上的。这是因为创建整个数组时,operator new[]只会被调用一次,然后调用10次构造函数来初始化数组中的各个对象。当第1次调用构造函数时,onTheHeap被设为true,然后flag重置为false。后面的构造函数调用由于不会再调用operator new[]了,所以都会把onTheHeap设为false。
(2)事实上,如果要实现在任何情况下都能判断对象是否在堆上,在C++中很难存在一种完全可移植的方案。比如,很多系统上,程序的地址空间都是按线性的顺序排列,栈从地址空间的高端往下增长,堆则从低端往上增长。这就可能通过把对象的地址(通过取址运算符&获得)与栈上的一个变量地址进行比较,若比栈变量地址小,则说明对象肯定在堆上。但并不是所有的系统都这样组织内存,而且很多系统的程序静态存储区会放在堆以下的地址空间中,这样就无法判断静态的栈对象了。这就是一种不可移植的方案,它依赖于系统的底层实现。一般UPNumber类中的实现基本能满足我们要求。因为只要通过第1个对象判断出了整个数组块是分配在堆上的,就自然知道了数组中的每个对象都是在堆上的(虽然它们的判断输出结果有出入)。
(3)按照设计惯例,重写operator new和operator new[],也要重写operator delete和operator delete[],以保持对称。但这里我们只是设置了一下标志,并没有做其他定制性的工作,因此不对称也没关系。另一方面,要注意类的这些成员new和delete函数默认为static的,因为它们在构造对象之前或撤销对象之后运行,没有非静态的成员数据可操纵。
我们可以把堆判断的功能抽离出来,设计成一个基类。为了使设计更完善,我们直接用一个列表来保存堆上的对象的地址,在重写的operator new中把分配的对象堆内存指针压入列表中。在重写的opeator delete中只要搜索这个列表,看看列表中有没有它的内存指针,有就说明是一个堆对象,需要释放内存,没有就说明是一个栈对象。
- //heaptracked.hpp:HeapTracked类可判断单个的对象是否的在堆上
- #ifndef HEAP_TRACKED_HPP
- #define HEAP_TRACKED_HPP
- #include <cstddef>
- #include <list>
- class HeapTracked{
- private:
- static std::list<const void*> addresses; //存放各个堆对象的指针
- public:
- class MissingAddress{ }; //异常类
- virtual ~HeapTracked()=0; //纯虚函数
- static void* operator new(std::size_t size);
- static void operator delete(void *ptr);
- bool isOnHeap() const;
- };
- std::list<const void*> HeapTracked::addresses; //静态对象在类外仍需定义
- HeapTracked::~HeapTracked(){ //纯虚的析构函数必须要有再定义
- }
- void* HeapTracked::operator new(std::size_t size){
- void *memPtr=::operator new(size); //分配内存
- addresses.push_front(memPtr); //把地址压入列表前端
- return memPtr;
- }
- void HeapTracked::operator delete(void *ptr){
- if(ptr==0) return;
- //在列表中查找是否有这个指针
- std::list<const void*>::iterator it=
- find(addresses.begin(),addresses.end(),ptr);
- if(it!=addresses.end()){ //若找到,说明指针指向了堆上的内存
- addresses.erase(it); //从列表中移除这个指针
- ::operator delete(ptr); //释放指针指向的内存
- }else{ //否则ptr不是一个堆对象的指针,不能调用operator delete,抛出异常
- throw MissingAddress();
- }
- }
- bool HeapTracked::isOnHeap() const{
- //获取*this对象的真正内存起始地址
- const void* rawAddress=dynamic_cast<const void*>(this);
- //在列表中查找this指针
- std::list<const void*>::iterator it=
- find(addresses.begin(),addresses.end(),rawAddress);
- return it!=addresses.end();
- }
- #endif
- //heaptrackedtest.cpp:对堆跟踪器HeapTracked类的测试
- #include <iostream>
- #include "heaptracked.hpp"
- class Asset : public HeapTracked{
- private:
- int value;
- public:
- //...
- };
- int main(){
- Asset a;
- std::cout<<a.isOnHeap()<<std::endl; //不在堆上
- Asset *b=new Asset;
- std::cout<<b->isOnHeap()<<std::endl; //在堆上
- delete b;
- return 0;
- }
HeapTracked是抽象混合基类,因为它有纯虚函数,因此是抽象基类,不能被实例化,只能被继承,由子类来创建对象。它的很多非虚的成员函数有功能实现,因此又是一般的基类,这些功能代表了各个子类的共性,因此把它称为抽象混合基类。注意当把析构函数声明为纯虚的时,必须同时要有定义,因为子类一定会调用基类的析构函数,这样它就必须有定义,而不只是声明。在使用时,让需要堆判断功能的类直接继承HeapTracked类即可。注意这个类只能判断单个对象是否在堆上,不能判断数组是否在堆上,当然可以通过重写operator new[]来完善。唯一需要解释的就是那个dynamic_cast,它把this指针转换成const void*。因为由多继承或虚基类继承而来的对象会有多个地址,因此我们必须要把一个指针dynamic_cast成void*类型,这就会使它指向对象的真正内存开始处。
3、禁止对象分配在堆上: 这个比较容易。把operator new/operator delete、operator new[]/operator delete[]都声明为私有即可。注意这样的类不能作为基类,也不能被其他类包含。
====================================================================================================================