C ++中的兼容变量长度结构

时间:2013-11-14 04:14:00

标签: c++ data-structures struct standards standards-compliance

在标准C中,您可以使用大小为0的数组结束结构,然后重新分配它以向数组添加可变长度维度:

struct var
{
    int a;
    int b[];
}

struct var * x=malloc(sizeof(var+27*sizeof(int)));

如何以标准(便携式)方式在C ++中实现这一目标? 可以有一个最大可能大小的约束,显然不需要在堆栈上工作

我在考虑:

class var
{
...
private:
  int a;
  int b[MAX];
};

然后根据所需的大小使用allocators或重载new / delete来分配:

(sizeof(var) - (MAX-27)* sizeof(int)

但是,虽然它似乎有用,但它不是我想要维护的东西。

是否有更完全标准/便携的清洁方式?

8 个答案:

答案 0 :(得分:3)

简单地做C路的变种有什么问题?

如果结构必须保持纯粹的POD,那么C方式就可以了。

struct var
{
    int a;
    int b[1];

    static std::shared_ptr<var> make_var(int num_b) {
        const extra_bytes = (num_b ? num_b-1 : 0)*sizeof(int);
        return std::shared_ptr<var>(
                new char[sizeof(var)+extra_bytes ],
                [](var* p){delete[]((char*)(p));});
}

因为它是POD,所以一切都像在C中一样。


如果b不能保证是POD,那么事情会变得更有趣。我没有测试任何它,但它看起来或多或少是这样的。请注意make_var依赖于make_unique,因为它使用lambda析构函数。没有它你可以让它工作,但它是更多的代码。这就像C方式一样,除了它用构造函数和析构函数干净地处理可变数量的类型,并处理异常

template<class T>
struct var {
    int a;

    T& get_b(int index) {return *ptr(index);}
    const T& get_b(int index) const {return *ptr(index);}

    static std::shared_ptr<var> make_var(int num_b);
private:
    T* ptr(int index) {return static_cast<T*>(static_cast<void*>(&b))+i;}
    var(int l);
    ~var();
    var(const var&) = delete;
    var& operator=(const var&) = delete;

    typedef typename std::aligned_storage<sizeof(T), std::alignof(T)>::type buffer_type;
    int len;
    buffer_type b[1];
};
template<class T> var::var(int l)
    :len(0)
{
    try {
        for (len=0; len<l; ++len)
            new(ptr(i))T();
    }catch(...) {
        for (--len ; len>=0; --len)
            ptr(i)->~T();
        throw;
    }
}
template<class T> var::~var()
{
    for ( ; len>=0; --len)
        ptr(i)->~T();
}
template<class T> std::shared_ptr<var> var::make_var(int num_b)
{
    const extra_bytes = (num_b ? num_b-1 : 0)*sizeof(int);
    auto buffer = std::make_unique(new char[sizeof(var)+extra_bytes ]);
    auto ptr = std::make_unique(new(&*buffer)var(num_b), [](var*p){p->~var();});
    std::shared_ptr<var> r(ptr.get(), [](var* p){p->~var(); delete[]((char*)(p));});
    ptr.release();
    buffer.release;
    return std::move(r);
}

由于这是未经测试的,它可能甚至不编译,并且可能有错误。我通常使用std::unique_ptr但是我懒得制作正确的独立删除器,并且当删除器是lambda时,unique_ptr很难从函数返回。如果您想使用这样的代码,请使用适当的独立删除程序。

答案 1 :(得分:2)

虽然这不是直接回答你的问题 - 我想指出C ++中更好的做法是将STL lib用于这种可变长度数组 - 任何维护者都会安全,简单和理解在你之后。

class var
{
...
private:
  int a;
  std::vector<int> b; // or use std::deque if more to your liking
};

现在你可以像其他任何类一样新建它;

var* myvar = new var;

你可以像旧类型数组一样使用它而不显式分配内存(虽然这不是大多数++程序员所做的)

myvar->b[0] = 123;
myvar->b[1] = 123;
myvar->b[2] = 123;

答案 2 :(得分:1)

是的,你可以,虽然你不能将它声明为数组成员。您可以使用参考:

struct s {
    int ( & extra_arr )[];

    s() : extra_arr( reinterpret_cast< int (&)[] >( this[1] ) {}
};

在实践中,这将使用指针的存储价值,虽然理论上它不需要。这个课程不是POD,可归因于理论与实践之间的差异。


您可以将reinterpret_cast替换为非静态成员函数:

struct s {
    int ( & get_extra() )[]
        { return reinterpret_cast< int (&)[] >( this[1] ); }

    int const ( & get_extra() const )[]
        { return reinterpret_cast< int const (&)[] >( this[1] ); }
};

现在访问需要函数调用语法(内联将消除除了调试版本之外的机器代码中的区别),但是没有浪费的存储,并且对象将是POD禁止POD规则的其他例外。

通过一些ABI调整,例如#pragma pack,这可以获得完整的C二进制兼容性。无论如何,序列化应用程序通常都需要这样的调整。

此外,这个支持const正确性,而前面的解决方案允许修改const对象(因为它不知道数组是同一个对象的一部分)。

样板文件可以推广到CRTP基类(在C ++ 11中甚至仍允许派生类为POD),或者预处理器宏扩展为定义C ++访问器或C flexible成员。


请注意,这些解决方案都不会比原始C更多。特殊成员函数不会复制灵活数组,并且该类不支持函数参数或子对象。

答案 3 :(得分:0)

更清洁的方法是使用继承:

class Parent
{
  public:
    virtual int get_b(unsigned int index) = 0;
  protected:  
    int a;
};

class Child1
: public Parent
{
  public:
    int get_b(unsigned int index)
    {
        return b[index];  // Should have index bounds checking.
    }
  private:
    int b[20];
};

继承允许您调整Parent类成员的大小和数量。

答案 4 :(得分:0)

好的 - (由于我不确定,所以没有提出这个问题)因为在目前的答案中我认为目前没有比超过分配更好的方法,所以我想知道如果这有助于维护明智:

template <class BASE, class T>
class dynarray
{
public:
    BASE base;
    const size_t size;
    T data[1]; // will be over allocated

    static dynarray * create(size_t data_size)
    {
        return new(data_size) dynarray(data_size);
    }
    void operator delete(void *p)
    {
        ::operator delete(p);
    }
private:
    void * operator new (size_t full_size, size_t actual)
    {
        if (full_size != sizeof(dynarray))
        {
            // inheritence changed size - allocate it all
            return ::operator new(sizeof(dynarray));
        }

        return ::operator new(sizeof(dynarray) + (actual-1)*sizeof(T));
    }
    void operator delete(void *p, size_t) // matching delete
    {
        ::operator delete(p);
    }
    dynarray(size_t data_size) : size(data_size)
    {
    }
};

用法有点笨拙,但可能更好:

typedef dynarray<double,int,27> dyn;
dyn * x=dyn::create(7);
x->data[5]=28;
x->base=5.3;

编辑:将实施从 alloaction改为 over 分配

答案 5 :(得分:0)

另一种方法是使用贴图新

#include <cstdlib>

class var
{
    ...
private:
    int a;
    int b[1];
};

var * x = new(malloc(sizeof(var) + (27-1)*sizeof(var::b))) var;

在这种情况下,在分配的内存上调用构造函数

删除结构使用:

x->~var(); // only if var have a destructor
free(x);

或更好,在var中添加一个delete运算符并使用delete:

struct var {
    ...
    operator delete(void* ptr) throw() { free(ptr); }
};
var * x = ...
delete x;

最好和正确的方法是使用静态函数来创建实例并将构造函数私有化:     class var     {     上市:         ...         static var * create(int size,)throw(){             new(malloc(sizeof(var)+(27-1)* sizeof(b))))var();         }         void operator delete(void * ptr){free(ptr); }

private:
    int a;
    int b[1];
    var(<args>) { ... }
};

var * x = var::create(27);
delete x;

注意:我使用大小为1的数组,因为所有编译器都不支持未定义和0大小的数组。

答案 6 :(得分:0)

template <size_t MAX>
class var
{
   ...
private:
  int a;
  int b[MAX];
};

在每个模板实例化中,MAX是一个可以在循环上使用的常量。然后你可以构建任何长度的变量。

var<7> v7;
var<100> v100;

或者输入他们

typedef var<10> myVar;

答案 7 :(得分:-2)

此处为C:http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_051.html

提交了一份缺陷报告

响应是声明数组是你需要的最大尺寸(或者实际上你也可以为整数做最大可能的尺寸)是一种“更安全的习惯用法”并且严格遵守。我们的想法是,代替 over 分配并因此超出声明的数组大小,实际上 下的分配并且只访问数组声明的边界内的内存。

这应该适用于C ++,只要它没有改变那些它不应该具有的规则,因为它意味着与C非常兼容。如果有人知道某些特定于C ++的东西会使这个解决方案无效,请通知我。

只要您将此实现隐藏在定义良好的界面后面,就不会出现任何维护问题。