【问题标题】:Accessing memory past the class通过类访问内存
【发布时间】:2012-01-15 12:07:26
【问题描述】:

我有一个特殊的问题。我想在其中创建具有可变长度数组的类。出于局部性的原因,我不想在堆上分配数组(当我这样做时,代码会减慢 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 确定第一个成员变量在其派生类中的位置?

【问题讨论】:

  • 测量虚函数调用的成本了吗?
  • 这总是很难看的。
  • Derived 具有固定大小时,为什么Base 必须具有“未知”大小?看起来您不需要 VLA。
  • @Matteo 不,我没有。我承认它可能并不昂贵(我会仔细检查)。现在将其视为纯粹的学术练习。
  • 我不太明白为什么这 a) 如此复杂 b) 您的代码的哪个方面是“动态的”。缓冲区大小似乎是一个模板参数,即编译时常量。如果这就是你想要的,这可能会容易得多......我一定错过了一些东西,但如果你能解释得更好一点,我将不胜感激。

标签: c++ memory alignment padding


【解决方案1】:

我想到了一个想法:让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;
}

【讨论】:

  • 如果我将 int 替换为数组的内容,这仍然有效吗?
  • 哦,我看错了你在做什么。忽略我之前的评论。
  • 优秀的解决方案!简单便携。我接受(但系统说我必须等待 3 分钟)。
【解决方案2】:

根据您的评论,听起来像这样就足够了:

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

【讨论】:

  • ... 这基本上就是我的回答。 :)
  • 对,这基本上是上面 Matteo 的回答。它确实有效。我仍然很好奇是否可以在没有额外指针的情况下做到这一点,但这纯粹是好奇。
  • @MatteoItalia:确实如此。在考虑 OP 的问题时,我没有查看您的答案;对不起。
  • @foxcub:不,不便携。基本访问器函数只能引用它知道的东西。
  • 嗯,它知道 sizeof(Base)。标准中对派生类中的填充没有要求吗?
【解决方案3】:

这是一个危险但又快又小的答案:

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

对于 Windows,您必须将 alloca 替换为 _alloca。请记住,这很容易被滥用和破坏,如果使用大量数据可能会导致堆栈溢出。除了速度测试之外,我确实推荐这个,而且可能也不推荐。

【讨论】:

  • 我实际上需要使用自定义分配器,所以 alloca 不适合我。
  • 自定义分配器?在堆栈上?
  • 我不希望这些对象在堆栈中。我希望阵列在物理上靠近 Base 的其余部分(或它位于里面的任何东西)。我追求的是局部性,而不是堆栈。
  • @foxcub:所以你只想要std::array&lt;obj, size&gt;
  • 不,大小是在运行时给出的。上面两种解决方案都达到了我想要的效果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-27
相关资源
最近更新 更多