boost::variant和boost::any是如何工作的?

68
boost库中的variant和any是怎样工作的?在我正在处理的项目中,我目前使用一个标记联合。我想使用其他东西,因为C++中联合不允许您使用具有构造函数、析构函数或重载赋值运算符的对象。
我查询了any和variant的大小,并对它们进行了一些实验。在我的平台上,variant占用其最长可能类型的大小加上8个字节:我认为它只是8个字节的类型信息和余下的部分是存储的值。另一方面,any只占用8个字节。由于我在64位平台上,我猜测any只是保存一个指针。
Any如何知道它持有的类型?Variant通过模板实现了什么?在使用它们之前,我想了解更多关于这些类的信息。
3个回答

82
如果您阅读boost::any的文档,它们提供了这个想法的源代码:http://www.two-sdg.demon.co.uk/curbralan/papers/ValuedConversions.pdf 这是基本的信息隐藏,这是必不可少的C++技能。学习它!
由于这里得票最高的答案完全不正确,并且我怀疑人们是否会去查看源代码来验证这一事实,因此这里提供了一个基本实现类似于任何类型接口的方法,它将包装任何具有f()函数的类型并允许调用它。
struct f_any
{
   f_any() : ptr() {}
   ~f_any() { delete ptr; }
   bool valid() const { return ptr != 0; }
   void f() { assert(ptr); ptr->f(); }

   struct placeholder
   {
     virtual ~placeholder() {}
     virtual void f() const = 0;
   };

   template < typename T >
   struct impl : placeholder
   {
     impl(T const& t) : val(t) {}
     void f() const { val.f(); }
     T val;
    };
   // ptr can now point to the entire family of 
   // struct types generated from impl<T>
   placeholder * ptr;

   template < typename T >
   f_any(T const& t) : ptr(new impl<T>(t)) {}

  // assignment, etc...
};

boost::any的基本功能与之相同,只是f()实际返回typeinfo const&,并提供其他信息访问以使any_cast函数工作。


6
顺便说一下,这只是类型擦除。一个非常有用的原则。 - Matthieu M.
我最近想知道为什么不使用unique_ptr而是使用原始指针?这样做有没有好的理由或者与性能有关?这可能可以避免delete,对吧? - Alex Botev
@Belov 我并不认为使用unique_ptr存在问题(虽然我不是智能指针的专家)。所以我认为这个答案最初发布时C++11标准还未被批准,自那以后没有人真正费心去修复这样一个小问题。 - niraami

19
boost::anyboost::variant的关键区别在于any可以存储任何类型,而variant只能存储一组列举出的类型之一。 any类型存储void*指向对象的指针,以及一个typeinfo对象,以记住底层类型并实施某种程度的类型安全性。在boost::variant中,它计算最大的大小对象,并使用“放置新的”将对象分配到此缓冲区中。它还存储类型或类型索引。

请注意,如果您安装了Boost,则应该能够在“any.hpp”和“variant.hpp”中看到源文件。只需在“/usr”,“/usr/local”和“/opt/local”中查找“include/boost/variant.hpp”和“include/boost/any.hpp”,直到找到已安装的标头,然后您可以查看。

编辑
正如下面的评论所指出的那样,在我的boost::any描述中有一个轻微的不准确之处。虽然它可以使用void*来实现(和一个模板化的销毁回调来正确删除指针),但实际的实现使用any<T> :: placeholder *,其中any<T> :: holder <T>作为any<T> :: placeholder 的子类,目的是统一类型。


4
你也可以在他们的网站上查看源代码。例如,这里是any.hpp的内容,以及这里是variant.hpp的内容#include指令已经添加了超链接,因此你可以直接查看它们所包含的文件。 - In silico
15
这个答案完全错误。任何情况下都不会使用空指针(void pointers)。 - Edward Strange
7
Crazy Eddie 是正确的,any 没有使用 void * 实现是因为这会使资源清理更加困难。相反,它使用了一个多态的类层次结构,并为每种存储类型使用派生类模板。这样可以通过虚析构函数进行清理。但是,你的答案其他部分非常好,所以我没有投下自己的反对票。 - templatetypedef
2
抱歉,我改正我的说法。我看过类似的实现,使用void指针和模板化销毁函数,但是你说得对,boost使用的是any::placeholder,而any::holder<T>是any::placeholder的子类。 - Michael Aaron Safyan
1
@IgorGanapolsky 隐式/自动转换只能做到这个程度。要转换这两个,您需要复制/转换元素。(顺便说一句,这是一件好事,因为它可以防止您无意中进行大量昂贵的复制)。 - Michael Aaron Safyan
显示剩余2条评论

8

boost::any仅在模板构造函数运行时快照typeinfo:它具有指向非模板基类的指针,该基类提供对typeinfo的访问,并且构造函数派生了一个满足该接口的特定类型的类。这种技术实际上可以用于捕获一组类型的其他常见功能(例如流式传输、公共运算符、特定函数),尽管boost不提供此类型的控制。

boost::variant在概念上与以前所做的类似,但通过不直接使用union,而是手动处理其缓冲区中对象的放置构造/销毁(同时明确处理对齐问题),它解决了C++在实际union中复杂类型的限制问题。


1
自从这个答案被写出来(可能),boost typeerasure让你可以捕获类型的其他属性。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接