Доступ к памяти после класса - PullRequest
3 голосов
/ 08 декабря 2011

У меня особенная проблема. Я хочу создать классы с массивами переменной длины в них. Я не хочу размещать массивы в куче по причинам локальности (код замедляется в 2 раза, когда я это делаю). Я не хочу использовать виртуальную функцию, потому что я не хочу платить за вызов функции. Следующий код работает (на моем компиляторе / платформе), но с оговоркой.

include <iostream>
include <boost/array.hpp>

struct Base
{
    Base(size_t s):
       size(s)                 {}
    int&        up(size_t i)   { return *(reinterpret_cast<int*>((&size) + 1) + i); }
    size_t      size;
};

template<size_t sz>
struct Derived: public Base
{
    boost::array<int, sz>       data;
    Derived(): Base(sz)         {}
};

int main()
{
    Derived<5> d5;
    d5.data[2] = 1234;
    Base* b = &d5;
    std::cout << b->up(2) << std::endl;
}

Это невероятно безобразно; reinterpret_cast <...> - это красный флаг. Более того, предостережение заключается в том, что это не удастся, если я изменю size_t, скажем, на short unsigned (я думаю, компилятор дополняет класс).

Так что вопрос: есть ли способ сделать этот портативный? Можно ли из Base определить, где будет находиться первая переменная-член в производном классе?

Ответы [ 3 ]

3 голосов
/ 08 декабря 2011

Мне пришла в голову идея: пусть конструктор Derived хранит указатель на свои данные внутри Base члена.

struct Base
{
protected:
    size_t size;
    int * array;
    Base(size_t s, int * arr):
      size(s), array(arr)
    { }

public:
    int&   up(size_t i)   { return array[i]; }
    size_t getSize() { return size; }
};

template<size_t sz>
struct Derived: public Base
{
    std::array<int, sz> data;

    Derived():
      Base(sz, &data[0])
    { }
};

int main()
{
    Derived<5> d5;
    d5.data[2] = 1234;
    Base* b = &d5;
    std::cout << b->up(2) << std::endl;
}
2 голосов
/ 08 декабря 2011

Из вашего комментария может показаться, что этого может быть достаточно:

#include <cstddef>
#include <array>
#include <algorithm>

template <typename T>
struct ArrayBase
{
  typedef T type;

  type & operator[](std::size_t i) { return buf[i]; }
  type const & operator[](std::size_t i) const { return buf[i]; }

protected:
  ArrayBase(type * p) : buf(p) { }

private:
  type * buf;
};

template <typename T, std::size_t N>
struct Array : ArrayBase<T>
{
  Array()
  : ArrayBase<T>(a.data())
  {
  }

  Array(Array const & rhs)
  : ArrayBase<T>(a.data())
  {
      std::copy(rhs.a.begin(), rhs.a.end(), a.begin());
  }

private:

  std::array<T, N> a;
};

Использование:

Array<int, 5> a5;
ArrayBase<int> & b = a5;
b[2] = 11;
Array<int, 5> a52 = a5;
a52[2] = 13;
0 голосов
/ 08 декабря 2011

Вот опасный, но быстрый и маленький ответ:

template<class T>
struct alloca_magic {
    T* b;
    int s;
    alloca_magic(void* bu, int sz) 
       : b((T*)(bu)),s(sz) 
    {new(b)T[s];}
    ~alloca_magic() 
    {for(int i=0;i<s;++i)(b+i)->~T();}
    operator T*() {return b;}
};
#define alloca_magic(Type,Name,Size) void* t = alloca(sizeof(Type)*Size); alloca_magic<Type> Name(t,Size);

#include <iostream>     
struct test {
    int data;
    test() {std::cout << "ctor\n";}
    ~test() {std::cout << "dtor\n";}
};

void foo(int len) {
   std::cout << "begin foo\n";
   alloca_magic(test ,buffer,len);
    for(int i=0; i<len; i++)
       buffer[i].data = i;
   std::cout << "end foo\n";
}

int main() {
    int len; 
    std::cin >> len;
    std::cout << "begin main\n";
    alloca_magic(test ,buffer,len);
    for(int i=0; i<len; i++)
        buffer[i].data = i;
    foo(len);
    std::cout << "end main\n";
}

http://ideone.com/ccvTR Результаты:

begin main
ctor
ctor
ctor
begin foo
ctor
ctor
ctor
end foo
dtor
dtor
dtor
end main
dtor
dtor
dtor

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

...