在标准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)
但是,虽然它似乎有用,但它不是我想要维护的东西。
是否有更完全标准/便携的清洁方式?
答案 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 ++的东西会使这个解决方案无效,请通知我。
只要您将此实现隐藏在定义良好的界面后面,就不会出现任何维护问题。