【问题标题】:Vector that can hold any POD可以容纳任何 POD 的向量
【发布时间】:2012-03-16 13:23:14
【问题描述】:

有没有一种可以容纳任何 POD 的向量?类似:

anyvector v;
v.push_back<int>(1);
v.push_back<somestruct>({1, 2, 3});

并通过以下方式访问它:

int a = v.get<int>(0);
somestruct b = v.get<somestruct>(1);

我知道保存每个元素的偏移量必须有开销,但它应该小于我当前的解决方案vector&lt;boost::any&gt; 的开销。
我需要的操作是插入到末尾、从末尾删除和随机访问。
我知道实施一个不是问题,只是询问是否有现成的。

编辑:使用指针存储数据(boost::anyboost::variant)的解决方案比使用线性存储的开销更大,而这正是我正在寻找的。​​p>

【问题讨论】:

  • 关注BOOST的AnyVariant
  • @megabyte1024:正如我所说,这是我目前的解决方案,但每个都带有一个指针,这对线性存储来说是一个巨大的开销。
  • 如果您使用的是 Qt,则为 QVariant。首先集中精力制作一个 Any-Type-Variable(Any、Variant、QVariant),然后将其放入向量中,然后简单地在该类型上使用 std::vector。
  • 你可以使用不同的编程语言吗?好像你是Greenspunning (c:
  • @steaanv:你说的是假的。考虑一个字节数组,其中包含端到端放置的不同大小的结构,加上一个 size_t 数组,其中依次包含每个元素的偏移量。这没有与任何最大大小相关的“大”内存开销,开销是每个元素一个偏移量。它在访问期间确实具有恒定的复杂性,它是O(1) 双重间接。最后插入是摊销O(size of the new element),最后取出是O(1)。我认为 Dani 知道这是可以做到的,并且知道如何做到这一点,问题是是否有一个 prêt-à-porter。

标签: c++ vector c++11


【解决方案1】:

我从来没有听说过,如果有的话我会很惊讶,因为 非常特别。但是,实施起来应该不会太难, 使用两个向量,std::vector&lt;unsigned char&gt; 用于原始内存 你把对象放在哪里,std::vector&lt;size_t&gt; 映射索引。 只要它只包含 POD,就可以使用 memcpy 插入 元素。只是不要忘记尊重对齐。而那个索引 数组映射索引:不要尝试将指针放入其中,因为这些会 被稍后的push_back 失效。

就此而言,实施一个具有 任何类型,甚至是非 POD,使用放置新和显式销毁。 唯一真正的问题是对齐。当您插入 向量,你还必须保存一个多态析构函数的实例 类型。这可能有点棘手,但是是可行的,至少在 实践。基本上,你会复制 boost::any 所做的事情,但是 而不是指向动态内存的指针,你会得到“动态内存” 来自第二个数组。

【讨论】:

  • 我预计的主要问题是需要调用析构函数,无论是否用于 POD:创建的对象需要被销毁,尽管通常不会为 POD 完成。如果做对了,我认为您已经有效地实施了std::vector&lt;boost::any, pool_allocator&gt;
  • @DietmarKühl IF 您可以保证对象具有琐碎的析构函数(恕我直言,这是一个很大的如果),那么您可以跳过析构函数。更重要的是:vector 使用的分配器不是问题;你正在实现的是使用你的分配器的boost::any 的等价物:基本上,你有一个any 的向量,其中你的any 使用unsigned char 的向量作为它的内存源,而不是标准new。 (当然,它必须将其“指针”保持为size_t,以避免在向量增长时失效。)
【解决方案2】:
std::vector<boost::variant<int,somestruct,...>> v;

v.push_back(1);
v.push_back(({1, 2, 3});

a = boost::get<int>(v[0]);

如果您知道在声明时必须处理哪些类型。

--编辑

正如 JohannesD 所说,boost::variant 会影响每个元素的大小,但是 正如 James Kanze 所说,保持偏移向量与 boost::any 非常相似,我不确定你真的可以更紧凑。

根据您的具体要求,另一种解决方案可能适合您。 在内部为每种类型存储一个不同的容器,这可能不会比直接使用多个向量好多少,但是您可以存储额外的信息,例如存储的最后几个元素。而且您仍然可以将所有内容集中在一个地方。
类似的东西:

template <template <class,class> class C, typename T, typename ...Args>
struct map_ { //FIXME: return a list of type C<T1>,C<T2>...
};

template <template <class...> class C, typename ...Args>
struct fill_ { // Hack to parametrize map by Args on GCC 4.6
 typedef T<Args> type;
};

template <typename... Ts>
struct hvec
{
 std::map<std::string,fill_<boost::variant,map_<std::vector,Ts>> vec_map;
 std::size_t last_id;
 std::string last_typeid;

 template <typename T>
 T get(std::size_t i)
 {
   std::vector<T>& v = vec_map[typeid(T).name];
   return vec[i];
 }

 template <typename T>
 std::size_t push_back(const T& e)
 {
   std::vector<T>& v = vec_map[typeid(T).name];
   v.push_back(e);
 }
};

当然,您可以用地图替换向量,从而记住元素 id,这将满足您的要求,但会增加额外的成本,并且仍然意味着一个“地址”/一个元素的键。但是,在这种情况下,您将永远不必记住给定偏移量处元素的类型。

【讨论】:

  • 正如我所说,它就像我当前的解决方案 (boost:any),但每个都带有一个指针,这对线性存储来说是一个巨大的开销。
  • @Dani 无论如何,您都会承担此开销,请参阅史蒂夫的回答。我认为不同的解决方案从根本上不会比 boost::anyboost::variant 更有效。
  • @Konrad:我的回答实际上是错误的。当我应该专注于实际需要的操作时,我专注于“一种向量”。随机访问会产生开销,但您有两种选择。您可以将数据分配到向量之外并具有指向它的固定大小的指针(如 boost::any),或者您可以将元素端到端放在一个大数组中并保存一个单独的数组映射索引以在数据。后者可能比单独分配的开销更少,这取决于删除只是在最后,这是普通内存分配器无法假设的。
  • @Dani - boost::variant 是基于堆栈的(当然,您的观点是正确的 - variant&lt;T...&gt; 始终使用 max(sizeof(T)...) 字节,与它包含的内容无关。)
【解决方案3】:

所以你不需要指针:这意味着你需要一个足够大的元素类型来直接包含任何 POD。鉴于 POD 对象可以具有任意大小,这显然是不可能的。

如果你有一组候选类型,看起来你可以有一个有界大小的可区分联合,但这不就是 Boost.Variant 所做的吗?文档表明它将在可能的情况下基于堆栈(因此可能是内联的)。你是如何衡量这个“大开销”的?

【讨论】:

  • 我不想要联合,我想要一个可以包含任意大小元素的向量
  • 如果元素大小不固定,则该容器将不会像向量或数组那样执行,或者在任何有意义的意义上be。它如何计算索引的偏移量?
  • 我认为他想要 O(n) 查找时间。或者也许 O(n) 额外的内存来存储偏移量。
  • 所以实际上不是向量:-)
  • @Useless 不是满足std::vector 标准要求的东西,不。存储和运行时的概念与向量的抽象数学概念没有任何关系,因此在这方面它们不应该改变任何东西。
【解决方案4】:

你可以试试vector和pair的组合

enum ElementType : unsigned int
{
  ET_INT,
  ET_WHATEVER
};

vector < pair < void*, unsigned int >* > v;
pair < void*, unsigned int > prv;
int i;

prv.first = &i;
prv.second = ET_INT;

v.push_back( &prv );

您可以选择按引用或按值存储对象。访问单曲元素是这样的:

int a = *( int* ) v.at( 0 )->first

这将为您提供固定的矢量元素大小和每个存储元素类型的信息。当通过引用存储对象时要注意生命周期和命名空间,但我更喜欢从类型转换并确保 void 指针是有效的,而不是每次都将超长的参数传递给函数。

希望对你有帮助;)

【讨论】:

    【解决方案5】:

    std::tuple 可以存储任意大小的元素序列,但只能存储一组固定的元素。

    在运行时具有任意类型的任意数量的元素听起来容易出错并且非常不安全。如果您执行以下操作,您预计会发生什么?

    anyvector v;
    v.push_back<Foo>(Foo());
    int i = v.back<int>();
    

    如果实现可以为您提供运行时异常,您会很幸运,而且我认为 anyvector 的实现无法保证这一点。例如,如果它尝试使用 RTTI,则会遇到一个问题,即实际上无法保证引用不同类型的两个 type_info 对象不相等。

    【讨论】:

      【解决方案6】:

      我认为要获得您想要的最佳近似值,您需要构建自己的数据结构。您可以基于两种实现类型来确定您的类型:

      std::vector<unsigned char> blobStorage_;
      std::vector<size_t> offsetMap_;
      

      这实际上是一个序列化 blob 实现,能够偏移访问序列化到 blob 中的单个对象。

      当你想 push_back 一个对象时,你

      1. 将其序列化到 blobStorage 的末尾
      2. 将偏移量(新对象 blob 的前面)添加到 offsetMap 中

      同样,当您想要访问您认为是矢量[n] 的元素时,您

      1. 从offsetMap_[n]获取偏移量
      2. 将对象反序列化为来自 blobStorage_[offset] 的提供类型

      这两个操作都是 O(sizeof(object)) 阶,但实际上是常数。

      不过,通常,当您想做这样的事情时,您会将“实际”序列化为一个 blob,其中“实际”是指您实际上将序列化设计到您的程序中。这通常意味着您使用标准标头来存储类型,该标头提供类型 ID 和可能的大小信息。您通常会这样做的原因是因为您在序列化时丢失了类型信息,并且在您指定的使用模式中(在 get 中提供了类型),您正在构建一个非常脆弱的程序,其中包含许多不成文的合同.您通常不希望您的程序在与他人合作的专业环境中容易被破坏。因此,您可以通过在更大的序列化设计中存储类型 ID 并使用其类型 ID 注册类型来使其更加健壮。然后,“向量”将知道如何反序列化并呈现正确类型的对象。

      【讨论】:

        【解决方案7】:
        • std::vector 将元素存储为连续数组
        • 在这样的数组中,每个元素都有一个起始地址 sizeof(Elem) 过去的前一个元素
        • POD 类型可以任意大
        • 没有办法拥有任意大对象的向量,因为sizeof(Elem) 是无限的。

        除非你能进一步限制自己,否则答案是否定的。

        如果您可以设置大小上限,那么可以,无需任何开销。 POD 对象可以在没有显式构造的情况下使用,也可以在没有显式破坏的情况下被杀死。因此,您只需创建一个包含 std::array&lt; char, N &gt;reinterpret_cast 元素的向量即可。

        typedef std::vector< std::array< char, pod_size_max > > pod_variant_vector;
        
        template< typename destination >
        destination &get( pod_variant_vector &vec, std::size_t index )
            { return reinterpret_cast< destination & >( vec[ index ] ); }
        
        template< typename destination >
        destination const &get( pod_variant_vector const &vec, std::size_t index )
            { return reinterpret_cast< destination const & >( vec[ index ] ); }
        

        确保在使用与getting 相同的类型对其进行初始化之前,不要使用任何此类对象的值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-07-28
          • 2017-08-09
          • 2011-10-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多