7

在标准 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];
};

然后使用分配器或重载 new/delete 以根据所需的大小分配不足:

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

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

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

4

8 回答 8

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,但我懒得制作适当的独立删除器,并且unique_ptr当删除器是 lambda 时,很难从函数返回。如果您想使用这样的代码,请使用适当的独立删除器。

于 2013-11-14T05:50:28.743 回答
2

虽然这不是直接回答你的问题——我要指出的是,在 C++ 中更好的做法是将 STL 库用于这种可变长度数组——它是安全和简单的,并且在你之后维护它的任何人都可以理解.

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;
于 2013-11-14T04:20:19.873 回答
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 灵活成员的预处理器宏。


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

于 2013-11-14T04:52:47.080 回答
1
template <size_t MAX>
class var
{
   ...
private:
  int a;
  int b[MAX];
};

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

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

或者 typedef 他们

typedef var<10> myVar;
于 2014-09-19T08:34:09.793 回答
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;

编辑:将实施从分配不足更改为过度分配

于 2013-11-14T06:36:53.620 回答
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 类成员的大小和数量。

于 2013-11-14T04:23:10.677 回答
0

另一种方法是使用placement new

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

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

最好和正确的方法是使用静态函数来创建实例并将构造函数私有化: class var { public: ... 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 大小的数组。

于 2014-04-02T16:58:29.730 回答
-2

此处针对 C 提交了一份缺陷报告:http ://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_051.html

响应是,将数组声明为您需要的最大大小(或者实际上您也可以为整数做最大可能大小)是一个“更安全的习惯用法”并且它是严格符合的。这个想法是,不是过度分配并因此超出数组的声明大小,而是实际上分配不足并且仅访问数组声明边界内的内存。

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

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

于 2013-11-14T04:34:23.510 回答