首先,Any Type是一种类型,可以用于表达任何类型,如简单类型中的int, float, double 以及自定义的类。Any Type常用于数据库操作,如用于表达查询数据库的输出。
其次,Any Type肯定是一种容器。Any Type是一种抽象概念,要表示任何类型,这种定义肯定不可能,只能是“能够容纳任何类型”,比如STL的容器。虽然STL容器能够满足这种需要,但STL容器缺少一层“类型”的内容,即所谓的cast操作。如果我用AnyType使用为我的类myClass,必须能够把AnyType转换为myClass,并且,如果是转换为otherClass等其他类型时能跑出异常。
综上所述,实现AnyType的两个关键点是:
容易误导的是:由于要容纳任意类型,所以AnyType肯定是一个模板类,利用C++的模板实现。但是这样的结果是无处不在的类型指定,例如vector<int>。
当然可以typedef, 但是在每次实例化的时候typedef稍显累赘。
解决之道在于把AnyType定义为一般的类,但是构造函数是模板函数。如boost(http://www.boost.org/libs/any)的实现:
template<typename ValueType> any(const ValueType & value) : content(new holder<ValueType>(value)) { } |
模板参数是值类型ValueType。从上面的构造函数中可以看输出,此构造的作用是把一个值转换为一个内部的类型变量content。那么,这个内部类型应该用什么呢?这里应该有三种选择:
1) ValueType* content
2) void * content
3) SomeClass* content
如果AnyType是模板类,我们可以使用1),可惜不是。
如果用2)类型信息就全部丢失了,随后的cast就不能实现了。
最后只剩下3)了,也即用类来承载。那么这个类具有什么特征呢?有哪些接口呢?
第一,为了封装性,这个类应该是AnyType类的内部类,也即指在AnyType内部可见。
第二,这个应该是模板类,因为它将承载任意类型;这里不需要再次使用模板构造函数的方法,因为它是以内部类。
第三,这个类必须有一个接口来提取类型信息,用于类型转换。
第四,这个类必须有一个接口来复制自己,方便使用者拷贝复制,实现深拷贝。
基于面向接口的设计思想,可以先定义借口,再定义实现。
class placeholder { public: // structors
virtual ~placeholder() { }
public: // queries
virtual const std::type_info & type() const = 0;
virtual placeholder * clone() const = 0;
};
|
看看boost的实现:类holder用ValueType成员held记录值;用标准C++的typeid来取类型信息;调用ValueType的拷贝构造来clone。
template<typename ValueType> class holder : public placeholder { public: // structors
holder(const ValueType & value) : held(value) { }
public: // queries
virtual const std::type_info & type() const { return typeid(ValueType); }
virtual placeholder * clone() const { return new holder(held); }
public: // representation
ValueType held;
}; |
到这里,AnyType已经成功的保存了任何类型的value;剩下的工作就是提供转换的接口。
说到类型转换,标准C++有4个常用的类型转换符:
这4类操作符对指针和引用进行转换。这里可以借助static_cast实现AnyType到ValueType的转换;当然,这是一个模板函数。原理很简单:取出AnyType的content的held的地址即可。由于boost多了一次接口placeholder,所以需要转换成其派生类指针之后才使用。这里boost使用的是static_cast而非dynamic_cast,这应该是错误的。dynamic_cast用于继承体系中的向下转换,用在这里正好合适;但是
template<typename ValueType> ValueType * unsafe_any_cast(any * operand) { return &static_cast<any::holder<ValueType> *>(operand->content)->held; } |
dynamic_cast比static_cast的效率稍低。static_cast在更宽的范围内完成映射,但往往是不安全的。
这点瑕疵暂且忽略。为何说是不安全的呢?这是因为它没有检测AnyType的typeid是否跟ValueType一致就武断的映射了。对比一下如下版本:
template<typename ValueType> ValueType * any_cast(any * operand) { return operand && operand->type() == typeid(ValueType) ? &static_cast<any::holder<ValueType> *>(operand->content)->held : 0; } |
唯一的不同施加了typeid的比较。
1) 要实现AnyType,必须使用模板类;为了使用方便,把模板类定义为AnyType的内部类;再AnyType的模板构造函数实例化该模板类。
2) 基于RTTI的typeid记录类型信息,用于随后的转换操作。