2016-10-02 6 views
2

Я пытаюсь создать структуру данных, где она будет содержать N количество разных типов в непрерывной памяти. Поэтому во время компиляции я могу сказать, что хочу хранить 4 элемента из 3 разных типов, а в памяти это будет выглядеть как 111122223333.Хранить произвольные элементы в непрерывной памяти

Я собираюсь использовать вариационный шаблонный подход, который, я думаю, будет делать то, что я хочу , однако я не уверен, как добавить элементы в каждый массив в методе добавления.

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

Даже если я получу это для работы, будет ли макет памяти правильной? Есть ли лучший подход?

+0

модифицированного мои ответьте на возможную проблему, указанную Ildjarn; вкратце: ** никогда не использовать мое решение, если в 'Args ...' не являются типы POD – max66

+1

Немного контекста, почему я задал вопрос и почему я принял ответ, даже с предупреждениями. В конечном итоге это будет использоваться для передачи буфера в OpenGL, а приведенный выше код является лишь примером. В реальном коде я проверю, что тип 'sizeof' делится на тип, который я хочу передать OpenGL (в настоящее время плавающий), так что это должно облегчить проблемы с выравниванием. Что касается проблемы с использованием таких вещей, как 'std :: string' как типа, это действительно так, и я считаю, что если я утверждаю на' std :: is_trivially_copyable', это должно быть безопасным. На практике различными типами будут векторы разного размера. – dempzorz

ответ

2

Я не думаю, что это хорошая идея, но ... я показываю это просто для удовольствия

Использование 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; 
} 

--- EDIT ---: добавлен метод, toCout(), который печатает (до std::cout) все сохраненные значения; просто чтобы предложить, как использовать значения.

--- EDIT 2 ---: (спасибо!) Как указано на Илдъярн это решение очень опасно, если в Args... типов некоторые не POD (Plain Old Data) типа.

Это хорошо объяснено в this page.

Я переписываю соответствующей части

В качестве примера такого типа, который не может быть безопасно скопирован с помощью тетсра является зОй :: строки. Обычно это реализуется с использованием общего общего указателя , и в этом случае он будет иметь конструктор копирования, который заставляет счетчик увеличиваться. Если копия была сделана с использованием memcpy , тогда конструктор копирования не будет вызываться, а счетчик будет слева со значением, которое будет ниже, чем должно быть. Вероятно, это приведет к , что приведет к преждевременному освобождению блока памяти, содержащего знаковые данные .

--- EDIT 3 ---

Как указано на Илдъярн (спасибо еще раз!) С этим решением, очень опасно оставлять data() члена.

Если кто-нибудь использовать указатель, возвращенный таким образом

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

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

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

может, в какой-то архитектуры, вызывают доступ к float * в качестве выровненным адрес, который может убить программу

Правильное (и безопасное) способ восстановить значения из указателя, возвращаемого data() является один используется в toCoutHelper(), используя `зЬй :: тетсра()

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

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

    float f; 

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

Сладкий! Я почти уверен, что это сработает для меня. – dempzorz

+0

@ildjarn - У меня такой же страх, и по этой причине я отговариваю это (просто для удовольствия) решение. Но ... какая именно инструкция, по вашему мнению, опасна? И почему? – max66

+0

@ildjarn - не уверен, что понял ... как вы думаете, это опасно? Std :: memcpy (buffer.data() + pos, & v0, sizeof (T)); '? Но 'buffer.data()' (если я не ошибаюсь) является указателем на 'char'; 'memcpy()' может иметь проблемы с памятью с указателями 'char' тоже? – max66

2

Существует два типа словарей, которые могут вам помочь.
std::variant и std::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 
+0

Спасибо, хотя это может быть проблемой с потерей памяти. Например, если у меня был std :: vector >, где MyStruct содержал 5 членов, каждый элемент для float занимал бы 5-кратную память, верно? – dempzorz

+0

@dempzorz Да. Это связано с тем, что с одним выравниванием и размером для упрощения изменения размера памяти. – user2296177