当你需要一个可变的类型时,有三种可能的解决方案:
- 无限制的类型,如 void*. 这种方法不可能是类型安全的,应该象逃避灾难一样避免它。
- 可变的类型,即支持多种类型的存储和获取的类型。
- 支持转换的类型,如字符串类型与整数类型之间的转换。
Any实现了第二种方案,一个基于值的可变化的类型,无限可能的类型。这个库通常用于把不同类型的东西存储到标准库的容器中。
Any 库如何改进你的程序
任意类型的类型安全存储以及安全的取回
在标准库容器中存放不同类型的方法
可以在无须知道类型的情况下传送类型
Any库提供一个类型, any, 它允许存入任意类型且稍后取回,而不损失类型安全性。它有点象是可变类型的化合物:它可以持有任意类型,但你必须知道类型才能取回值。有很多次你想在同一个容器中存入互不相关的类型。有很多次某些代码只想从一个指针向另一个指针传送数据,而不关心数据的类型。从表面看,这些事情很容易做。它们可以通过一个无类的类型来实现,如 void*. 它们也可以通过使用一个含有不同类型的union来实现。有很多可变类型通过一些类型标识机制来实现。不幸的是,所有这些都缺乏类型安全性,而只有在最可控的情形下我们才应该故意绕过类型系统。标准库的容器是要通过它们所包含的类型来特化的,这意味着不可能把不同类型的元素存入容器之内。幸运的是,解决的方案不一定要
void*, 因为 Any 库允许你将存入不同的类型而稍后取回。没有办法在不知道实际类型的情况下取回存入的值,类型安全从而得到保证。
在设计框架时,不可能预先知道哪些类型要和框架类一起使用。一个常见的方法是,要求框架的使用者遵守某种接口,或者从框架所提供的某个基类进行派生。这是合理的,因为框架可能需要与不同的高级类进行通信才能使用。但是也存在这样的情形,框架对于存入或接受的类型无须(或不能)知道任何相关信息。不要绕过类型系统去使用 void* 方法,框架可以使用 any 。
Any 如何适用于标准库
Any的一个重要特性是,它提供了存储不同类型的对象到标准库容器中的能力。它也是一种可变数据类型,这正是C++标准库非常需要而又缺乏的。
Boost.Any
类 any 允许对任意类型进行类型安全的存储和取回。不象无类类型,any 保存了类型信息,并且不会让你在不知道正确类型的情况下获得存入的值。当然,有办法可以让你询问关于类型的信息,也有测试保存的值的方法,但最终,调用者必须知道在 any 对象中的值的真实类型,否则不能访问any。可以把 any 看作为上锁的安全性。没有正确的钥匙,你不能进入其中。any 对它所保存的类型有以下要求:
- CopyConstructible 它必须可以复制这个类型
- Non-throwing destructor 就象所有析构函数应该的那样!
- Assignable 为了保证强异常安全(不符合可赋值要求的类型也可以用于 any, 但没有强异常安全的保证)
namespace boost { class any { public: any(); any(const any&); template<typename ValueType> any(const ValueType&); ~any(); any& swap(any &); any& operator=(const any&); template<typename ValueType> any& operator=(const ValueType&); bool empty() const; const std::type_info& type() const; }; }
成员函数
any();
缺省构造函数创建一个空的 any 实例,即一个不含有值的 any。当然,你无法从一个空的any中取回值,因为没有值存在。
any(const any& other);
创建一个已有 any 对象的独立拷贝。other 中含有的值被复制并存入 this.
template<typename ValueType> any(const ValueType&);
这个模板构造函数存入一个传入的ValueType类型参数的拷贝。参数是一个 const 引用,因此传入一个临时对象来存入any是合法的。注意,该构造函数不是 explicit 的,如果是的话, any 会难以使用,而且也不会增加安全性。
~any();
析构函数销毁含有的值,但注意,由于对一个裸指针的析构不会调用operator delete 或 operator delete[] ,所以在any中使用指针时,你应该把裸指针包装成一个象 shared_ptr 那样的智能指针。
any& swap(any& other);
交换存在两个 any 对象中的值。
any& operator=(const any& other);
如果any实例非空,则丢弃所存放的值,并存入other值的拷贝。
template<typename ValueType> any& operator=(const ValueType& value);
如果any实例非空,则丢弃所存放的值,并存入 value 的一份拷贝,value可以是任意符合any要求的类型。
bool empty() const;
给出any实例当前是否有值,不管是什么值。因而,当any持有一个指针时,即使该指针值为空,则 empty 也返回 false 。
const std::type_info& type() const;
给出所存值的类型。如果 any 为空,则类型为 void.
普通函数
template<typename ValueType> ValueType any_cast(const any& operand);
any_cast 让你访问any中存放的值。参数为需要取回值的 any 对象。如果类型 ValueType 与所存值不符,any 抛出一个 bad_any_cast 异常。请注意,这个语法有点象 dynamic_cast.
template<typename ValueType> const ValueType* any_cast(const any* operand);
any_cast 的一个重载,接受一个指向 any 的指针,并返回一个指向所存值的指针。如果 any 中的类型不是 ValueType, 则返回一个空指针。请再次注意,这个语法也有点象 dynamic_cast.
template<typename ValueType> ValueType* any_cast(any* operand);
any_cast 的另一个重载,与前一个版本相似,但前一个版本使用 const 指针的参数并返回 const 指针,这个版本则不是。
异常
bad_any_cast
当试图将一个any对象转换为该对象所存类型以外的其它类型,将抛出该异常。bad_any_cast 派生自 std::bad_cast. 注意,使用指针参数调用 any_cast 时,将不抛出异常(类似于对指针使用 dynamic_cast 时返回空指针一样),反之对引用类型使用 dynamic_cast 则会在失败时抛出异常。
用法
Any库定义在名字空间 boost 内。你要用类 any 来保存值,用模板函数 any_cast 来取回存放的值。为了使用 any, 要包含头文件 "boost/any.hpp". any 只允许你在知道类型的前提下访问它的值,这是很明智的。对于这个库,典型情况下你只需记住两件事:类 any, 用于存放值,还有模板函数 any_cast, 用于取回值。
在转型失败不是一种错误时,使用指针来传递 any, 但如果转型失败是一种错误,则应该使用const引用来传递,这样可以让 any_cast 在失败时抛出异常。如果你传递一个指针参数,你会得到一个指向保存值的指针;如果你传递一个 const 引用参数,你会得到一个保存值的拷贝。如果值的类型在拷贝时代价很昂贵,就应该传递指针以避免值的拷贝。
使用 any 让你能够在原来不能使用标准库容器和算法的地方下使用它们,从而让你可以写出更具有可维护性和更易懂的代码。
#include <iostream> #include <string> #include <utility> #include <vector> #include <boost/any.hpp> using namespace std; struct A { void some_function() { cout << "A::some_function()" << endl; } }; struct B { void some_function() { cout << "B::some_function()" << endl; } }; struct C { void some_function() { cout << "C::some_function()" << endl; } }; void print_any(boost::any& a) { if (A* pA=boost::any_cast<A>(&a)) { pA->some_function(); } else if (B* pB=boost::any_cast<B>(&a)) { pB->some_function(); } else if (C* pC=boost::any_cast<C>(&a)) { pC->some_function(); } else { try { cout << boost::any_cast<string>(a) << endl; } catch(boost::bad_any_cast &e) { cout << "Oops~" << e.what() << endl; } } } int main() { std::vector<boost::any> store_anything; store_anything.push_back(A()); store_anything.push_back(B()); store_anything.push_back(C()); store_anything.push_back(string("This is fantastic! ")); store_anything.push_back(3); store_anything.push_back(make_pair(true, 7.92)); for_each( store_anything.begin(), store_anything.end(), print_any); }
执行结果:
A::some_function()
B::some_function()
C::some_function()
This is fantastic!
Oops~boost::bad_any_cast: failed conversion using boost::any_cast
Oops~boost::bad_any_cast: failed conversion using boost::any_cast
成员函数使用
#include <iostream> #include <string> #include <boost/any.hpp> using namespace std; int main() { boost::any a1(100); boost::any a2(std::string("200")); boost::any a3; cout << "a3 is " << (a3.empty() ? "empty" : "not empty" )<< endl; a1.swap(a2); try { string s=boost::any_cast<std::string>(a1); cout << "a1 contains a string: " << s << endl; } catch(boost::bad_any_cast& e) { std::cout << "a1 doesn't contain a string! " << e.what() << endl; } if (int* p=boost::any_cast<int>(&a2)) { cout << "a2 seems to have swapped contents with a1: " << *p << endl; } else { cout << "Nope, no int in a2" << endl; } if (typeid(int)==a2.type()) { cout << "a2's type_info equals the type_info of int" << endl; } }
执行结果:
a3 is empty
a1 contains a string: 200
a2 seems to have swapped contents with a1: 100
a2‘s type_info equals the type_info of int
保存指针
通常,测试 empty 足以知道对象是否真的包含有效的东西。但是,如果 any 持有的是一个指针,则要在解引用它之前额外小心地测试这个指针。仅仅简单地测试 any 是否为空是不够的,因为一个 any 在持有一个指针时会被认为是非空的,即使这个指针是空的。
在 any 中保存裸指针的另一个麻烦在于析构的语义。any 类接受了它所存值的所有权,因为它保持一个该值的内部拷贝,并与 any 一起销毁它。但是,销毁一个裸指针并不会对它调用 delete 或 delete[] !它仅仅要求归还属于指针的那点内存。这使得在 any 中保存指针是有问题的,所以更好的办法是使用智能指针来代替。的确,使用智能指针是在一个 any 中保存指针的好办法。它解决了要保证所存指针指向的内存被正确释放的问题。当智能指针被销毁时,它会正确地确保内存及其中的数据被正确销毁。作为对比,要注意
std::auto_ptr 不是合适的智能指针。这是因为 auto_ptr 没有通常的复制语义;访问一个 any 中的值会把内存及其中数据的所有权从 any 转移到返回的 auto_ptr 中。
#include <iostream> #include <string> #include <algorithm> #include <vector> #include <boost/any.hpp> #include <boost/shared_ptr.hpp> using namespace std; class A { private: string m_str; public: A(string str):m_str(str){} virtual ~A() {cout << "A::~A()---" << m_str << endl; } void not_virtual() {cout << "A::not_virtual()---" << m_str << endl;} virtual void is_virtual () {cout << "A:: is_virtual ()---" << m_str<< endl;} }; class B : public A { private: string m_str; public: B(string str):A(str),m_str(str){} ~B(){cout << "B::~B()---" << m_str << endl;} void not_virtual() {cout << "B::not_virtual()---" << m_str << endl;} virtual void is_virtual () {cout << "B:: is_virtual ()---"<< m_str << endl;} }; void foo(boost::any& a) { try { boost::shared_ptr<A> ptr = boost::any_cast<boost::shared_ptr<A> >(a); ptr-> is_virtual (); ptr->not_virtual(); return; } catch(boost::bad_any_cast& e) {} try { boost::shared_ptr<B> ptr = boost::any_cast<boost::shared_ptr<B> >(a); ptr-> is_virtual (); ptr->not_virtual(); return; } catch(boost::bad_any_cast& e) {} cout << "~~~~~~~other types~~~~~~~~" << endl; } int main() { boost::any a1(boost::shared_ptr<A>(new A("a1"))); boost::any a2(string("Just a string")); { boost::any b1(boost::shared_ptr<A>(new B("b1"))); boost::any b2(boost::shared_ptr<B>(new B("b2"))); vector<boost::any> vec; vec.push_back(a1); vec.push_back(a2); vec.push_back(b1); vec.push_back(b2); for_each(vec.begin(),vec.end(),foo); cout << endl; } }
执行结果:
A:: is_virtual ()---a1
A::not_virtual()---a1
~~~~~~~other types~~~~~~~~
B:: is_virtual ()---b1
A::not_virtual()---b1
B:: is_virtual ()---b2
B::not_virtual()---b2
B::~B()---b2
A::~A()---b2
B::~B()---b1
A::~A()---b1
A::~A()---a1
vector 中的含有 string 的 any 。这显示了保存一些对稍后要被调用的函数而言是未知类型的类型到一个 any,是很有可能的,通常也是合理的;这些函数只需要处理它们需要操作的类型!
第三个元素含有一个指向 B 实例的 shared_ptr<A> 。这个例说明了 any 如何与其它类型一样实现多态性。当然,如果我们使用裸指针,就需要用 static_cast 来保存指针为我们想标识的类型。注意,函数 A::not_virtual 被调用而不是 B::not_virtual. 原因是这个指针的静态类型是 A*, 而不是 B*.
在里面的那个作用域结束时,vector 被销毁,它又销毁了内含的 any 实例,后者再销毁所有的 shared_ptr,正确地设置引用参数为零。接着,我们的指针被安全和不费力气地销毁!
这个例子显示了一些比如何与 any 一起使用智能指针更为重要的东西;它显示了存入 any 的类型有是简单的或是复杂的都无关紧要。如果复制被存值的代价是高得惊人的,或者如果需要共享使用和控制生存期,就应该考虑使用智能指针,就象使用标准库的容器保存值一样。同样的推理也适用于 any, 通常这两个原则是一致的,正如在容器中使用 any 就意味着要保存不同的类型。
总结
这个类型可以包含不同类型的值,而且与无类类型(如 void*)有很大不同。我们总是严重地依赖C++中的类型安全,只有在极少数情形下我们会愿意没有它来干活。这是有很好的原因的:类型安全防止我们犯错,并改善了我们代码的性能。因此,我们应该避免无类类型。还有,发现自己需要异类存储的情形很少见,或者为了将使用者隔离于类型的细节,或者为了在更低的层次获得极度的灵活性。any 提供了这些功能,同时维护了类型安全,它是我们的工具箱的最好扩充!
在以下情形时使用 Any 库:
- 你需要在容器中存放不同类型的值
- 需要保存未知类型
- 类型被传递到无须知晓任何有关该类型信息的层次
Any 的设计同时也是一门很有价值的课程,关于如何封装一个类型而不影响到该类型的封套类。这种设计可以用于创建泛型函数对象、泛型迭代器等等。它是一个展示封装的威力以及与模板相关的多态性的例子。
在标准库中,有很好的工具来存放多个元素。当需要存储异类的元素时,我们想避免使用新的集合类型。any 提供了一种方法,在大多数情况下它可以与已有容器一起使用。在某种程度上,模板类 any 扩展了标准库容器的能力,把不同的类型封入一个同类型的包装中,就可以把它们放入前述容器中了。把 Boost.Any 加到已有代码中是很简单的。它不需要修改设计,并且立即就增加了灵活性。接口非常小,这使得它成为一个很容易理解的工具。