将任意元素存储在连续的内存中

时间:2016-10-02 20:15:23

标签: c++ c++11 templates variadic-templates memory-alignment

我正在尝试创建一个数据结构,它将在连续的内存中保存N个不同的类型。所以在编译时我可以说我想存储3种不同类型的4个元素,在内存中它看起来像111122223333。

我一直在使用可变参数模板方法,我认为它会做我想要的,但是我不知道如何在add方法中为每个数组添加元素。

template<std::size_t N, typename... Args>
class Batch
{
    private:
        std::tuple<std::array<Args, N>...> data_;
        size_t currentPos_;

    public:
        template<typename T>
        void addToArray(std::array<T, N>& array, const T& value)
        {
            array[currentPos_] = value;
        }

        void add(const Args&... values)
        {
            //????
            addToArray(/*array, value*/);

            currentPos_++;
        }

        const void* data()
        {
            &return data_;
        }
};


int main()
{
    Batched<3, float, double, int> b;

    b.add(1.0f, 1.0, 1);
    b.add(2.0f, 2.0, 2);
    b.add(3.0f, 3.0, 3);
    b.add(4.0f, 4.0, 4);
    return 0;
}

即使我让它工作,内存布局是否正确?有更好的方法吗?

2 个答案:

答案 0 :(得分:3)

我认为这不是一个好主意但是......我只是为了好玩而展示它

使用std::vector<char>(以及对C ++ 11添加的方法data()授予的以下内存的访问权限)和古老的memcpy(),我想你可以简单地做如下

#include <vector>
#include <cstring>
#include <iostream>

template <typename... Args>
class Batch
 {
   private:
      std::vector<char> buffer;

   public:

      void addHelper ()
       { }

      template <typename T, typename ... Ts>
      void addHelper (T const & v0, Ts ... vs)
       { 
         auto  pos = buffer.size();

         buffer.resize(pos + sizeof(T));

         std::memcpy(buffer.data() + pos, & v0, sizeof(T));

         addHelper(vs...);
       }

      void add (const Args&... values)
       { addHelper(values...); }

      const void * data()
       { return buffer.data(); }

      void toCout ()
       { toCoutHelper<Args...>(0U, buffer.size()); }

      template <typename T, typename ... Ts>
      typename std::enable_if<(0U < sizeof...(Ts)), void>::type
         toCoutHelper (std::size_t  pos, std::size_t  size)
       {
         if ( pos < size )
          {
            T val;

            std::memcpy( & val, buffer.data() + pos, sizeof(T) );

            std::cout << " - " << val << std::endl;

            toCoutHelper<Ts...>(pos+sizeof(T), size);
          }
       }

      template <typename T, typename ... Ts>
      typename std::enable_if<0U == sizeof...(Ts), void>::type
         toCoutHelper (std::size_t  pos, std::size_t  size)
       {
         if ( pos < size )
          {
            T val;

            std::memcpy( & val, buffer.data() + pos, sizeof(T) );

            std::cout << " - " << val << std::endl;

            toCoutHelper<Args...>(pos+sizeof(T), size);
          }
       }

 };


int main()
 {
   Batch<float, double, int> b;

   b.add(1.0f, 1.0, 1);
   b.add(2.0f, 2.0, 2);
   b.add(3.0f, 3.0, 3);
   b.add(4.0f, 4.0, 4);

   b.toCout();

   return 0;
 }

---编辑--- :添加了一个方法toCout(),用于打印(到std::cout)所有存储的值;只是建议如何使用这些值。

---编辑2 --- :正如ildjarn指出的那样(谢谢!)如果在Args...类型中有一些非POD(普通旧数据),这个解决方案非常危险类型。

this page中解释得很清楚。

我转录相关部分

  

使用memcpy无法安全复制的类型的示例是   的std :: string。这通常使用引用计数来实现   共享指针,在这种情况下,它将具有一个复制构造函数   导致计数器递增。如果使用memcpy制作副本   那么复制构造函数将不会被调用,而计数器也将被调用   留下一个比它应该低的值。这可能会   导致包含该内存块的内存块过早释放   人物数据。

---编辑3 ---

正如ildjarn所指出的那样(再次感谢!)使用此解决方案离开data()成员非常危险。

如果有人使用以这种方式返回的指针

   char const * pv = (char const *)b.data();

   size_t  pos = { /* some value here */ };

   float  f { *(float*)(pv+pos) };  // <-- risk of unaligned access
在某些体系结构中,

可能会导致访问未绑定地址中的float *,从而导致该程序失效

使用`std :: memcpy()

data()中使用toCoutHelper()中使用的指针恢复值的正确(且安全)方法
   char const * pv = (char const *)b.data();

   size_t  pos = { /* some value here */ };

   float  f; 

   std::memcpy( & f, pv + pos, sizeof(f) );

答案 1 :(得分:2)

有两种词汇类型可以帮助你 std::variantstd::any

std :: variant更符合您的预期用途。

而不是像这样创建自己的类型:

Batched<3, float, double, int> b;

考虑使用:

std::vector<std::variant<float, double, int>> vec;

然后您可以正常添加元素:

vec.emplace_back(1);    //int
vec.emplace_back(1.0f); //float
vec.emplace_back(1.0);  //double