将任意元素存储在连续内存中 [英] Store arbitrary elements in contiguous memory

查看:124
本文介绍了将任意元素存储在连续内存中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

  template< std :: size_t N,typename ... Args> 
class Batch
{
private:
std :: tuple< std :: array< Args,N&数据_;
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;
}

即使我得到这个工作,内存布局是否正确?是否有更好的方法?

解决方案

我认为这不是一个好主意,



使用 std :: vector< char> (以及访问由C + +11添加方法 data())和好的 memcpy(),我想你可以简单地做

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

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

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;
}

--- EDIT --- :added一个方法, toCout()打印(到 std :: cout )所有存储的值;

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



此网页中解释得很好。



我转录相关部分


无法使用memcpy安全复制的类型示例是
std ::串。这通常使用引用计数的
共享指针实现,在这种情况下它将有一个复制构造函数,
使计数器递增。如果使用memcpy
创建副本,那么将不会调用副本构造函数,计数器将是
,其值低于它应为的值。这将很可能
导致包含
字符数据的内存块的过早解除分配。


--- EDIT 3 ---



如果ildjarn指出这个解决方案非常危险,



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

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

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

float f {*(float *)(pv + pos)}; //< - 不对齐访问的风险

在某些体系结构中, float * 在可以杀死程序的未对齐地址中



正确 data()返回的指针是 toCoutHelper()中使用的指针,使用`std :: memcpy

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

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

float f;

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


I am trying to create a data structure, where it will hold N number of different types in contiguous memory. So at compile time I can say I want to store 4 elements of 3 different types, and in memory it will look like 111122223333.

I've been going with a variadic template approach, which I think will do what I want, however I am not sure how to add the elements to each array in the add method.

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;
}

Even if I get this to work, will the memory layout be correct? Is there a better approach?

解决方案

I don't think it's a good idea but... I show it just for fun

Using a std::vector<char> (and the access to the following memory granted by the C++11 added method data()) and the good-old memcpy(), I suppose You can simply do as follow

#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;
 }

--- EDIT ---: added a method, toCout() that print (to std::cout) all the stored values; just to suggest how to use the values.

--- EDIT 2 ---: As pointed by ildjarn (thanks!) this solution is very dangerous if in the Args... types are some non POD (Plain Old Data) type.

It's explained well in this page.

I transcribe the relevant part

An example of a type that cannot be safely copied using memcpy is std::string. This is typically implemented using a reference-counted shared pointer, in which case it will have a copy constructor that causes the counter to be incremented. If a copy were made using memcpy then the copy constructor would not be called and the counter would be left with a value one lower than it should be. This would be likely to result in premature deallocation of the memory block that contains the character data.

--- EDIT 3 ---

As pointed by ildjarn (thanks again!) with this solution is very dangerous to leave the data() member.

If anyone use the pointer returned in this way

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

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

   float  f { *(float*)(pv+pos) };  // <-- risk of unaligned access

could, in some architecture, cause an access to a float * in an unaligned address that can kill the program

The correct (and safe) way to recover values from the pointer returned by data() is the one used in toCoutHelper(), using `std::memcpy()

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

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

   float  f; 

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

这篇关于将任意元素存储在连续内存中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆