这是在C ++中创建可变大小结构的最佳方法吗?我不想使用vector,因为初始化后长度不会改变。
struct Packet
{
unsigned int bytelength;
unsigned int data[];
};
Packet* CreatePacket(unsigned int length)
{
Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int));
output->bytelength = length;
return output;
}
编辑:重命名变量名称并更改代码更正确。
答案 0 :(得分:10)
关于你正在做什么的一些想法:
使用C风格的可变长度struct idiom允许您为每个数据包执行一次免费存储分配,这是struct Packet
包含std::vector
时所需数量的一半。如果要分配非常大量数据包,那么执行一半的免费存储分配/解除分配可能非常重要。如果您还在进行网络访问,那么等待网络所花费的时间可能会更加重要。
此结构表示数据包。您是否计划直接从套接字读取/写入struct Packet
?如果是这样,您可能需要考虑字节顺序。您在发送数据包时是否必须从主机转换为网络字节顺序,反之亦然?如果是这样,那么您可以在可变长度结构中对数据进行字节交换。如果将其转换为使用向量,则编写用于序列化/反序列化数据包的方法是有意义的。这些方法会将其传输到连续缓冲区或从连续缓冲区传输,并考虑字节顺序。
同样,您可能需要考虑对齐和打包。
您永远不能继承Packet
。如果你这样做,那么子类的成员变量将与数组重叠。
您可以使用malloc
和free
代替Packet* p = ::operator new(size)
和::operator delete(p)
,因为struct Packet
是POD类型,目前不会受益从默认构造函数和它的析构函数调用。这样做的(潜在)好处是全局operator new
使用全局新处理程序和/或异常处理错误,如果这对您很重要。
可以使变长度结构惯用法与new和delete运算符一起使用,但不是很好。您可以通过实现operator new
来创建一个采用数组长度的自定义static void* operator new(size_t size, unsigned int bitlength)
,但您仍然需要设置bitlength成员变量。如果使用构造函数执行此操作,则可以使用稍微冗余的表达式Packet* p = new(len) Packet(len)
来分配数据包。与使用全局operator new
和operator delete
相比,我看到的唯一好处是您的代码的客户可以只调用delete p
而不是::operator delete(p)
。将分配/解除分配包装在单独的函数中(而不是直接调用delete p
)只要它们被正确调用就可以了。
答案 1 :(得分:7)
如果您从未添加构造函数/析构函数,则使用malloc / free进行分配的赋值运算符或虚函数是安全的。
它在c ++圈子中不受欢迎,但我认为如果你在代码中记录它就可以使用它。
对您的代码提出一些意见:
struct Packet
{
unsigned int bitlength;
unsigned int data[];
};
如果我记得正确声明没有长度的数组是非标准的。它适用于大多数编译器,但可能会给你一个警告。如果要符合要求,请声明长度为1的数组。
Packet* CreatePacket(unsigned int length)
{
Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int));
output->bitlength = length;
return output;
}
这样可行,但您不会考虑结构的大小。将新成员添加到结构后,代码将中断。最好这样做:
Packet* CreatePacket(unsigned int length)
{
size_t s = sizeof (Packed) - sizeof (Packed.data);
Packet *output = (Packet*) malloc(s + length * sizeof(unsigned int));
output->bitlength = length;
return output;
}
在您的数据包结构定义中写一条注释,数据必须是最后一个成员。
顺便说一下 - 用一次分配来分配结构和数据是一件好事。您可以通过这种方式将分配数量减半,并且还可以改善数据的局部性。如果分配大量软件包,这可以提高性能。
不幸的是,c ++并没有提供一个很好的机制来做到这一点,所以你经常在现实世界的应用程序中使用这样的malloc / free hacks。
答案 2 :(得分:5)
这没关系(并且是C的标准做法)。
但这对C ++来说不是一个好主意 这是因为编译器会自动为您生成一整套其他方法。这些方法不明白你是否作弊。
例如:
void copyRHSToLeft(Packet& lhs,Packet& rhs)
{
lhs = rhs; // The compiler generated code for assignement kicks in here.
// Are your objects going to cope correctly??
}
Packet* a = CreatePacket(3);
Packet* b = CreatePacket(5);
copyRHSToLeft(*a,*b);
使用std :: vector<>它更安全,工作正常 我也打赌它会在优化器启动后与您的实现一样高效。
或者,boost包含固定大小的数组:
http://www.boost.org/doc/libs/1_38_0/doc/html/array.html
答案 3 :(得分:3)
如果您愿意,可以使用“C”方法,但为了安全起见,编译器不会尝试复制它:
struct Packet
{
unsigned int bytelength;
unsigned int data[];
private:
// Will cause compiler error if you misuse this struct
void Packet(const Packet&);
void operator=(const Packet&);
};
答案 4 :(得分:2)
如果您真正在使用C ++,除了默认成员可见性之外,类和结构之间没有实际区别 - 默认情况下,类具有私有可见性,而默认情况下结构具有公共可见性。以下是等效的:
struct PacketStruct
{
unsigned int bitlength;
unsigned int data[];
};
class PacketClass
{
public:
unsigned int bitlength;
unsigned int data[];
};
关键是,您不需要CreatePacket()。您可以使用构造函数初始化struct对象。
struct Packet
{
unsigned long bytelength;
unsigned char data[];
Packet(unsigned long length = 256) // default constructor replaces CreatePacket()
: bytelength(length),
data(new unsigned char[length])
{
}
~Packet() // destructor to avoid memory leak
{
delete [] data;
}
};
有几点需要注意。在C ++中,使用new而不是malloc。我采取了一些自由并将比特长度改为字节长度。如果这个类代表一个网络数据包,你处理字节而不是比特(在我看来)会好得多。数据数组是unsigned char数组,而不是unsigned int。同样,这是基于我的假设,即此类表示网络数据包。构造函数允许您创建这样的数据包:
Packet p; // default packet with 256-byte data array
Packet p(1024); // packet with 1024-byte data array
当Packet实例超出范围并且防止内存泄漏时,会自动调用析构函数。
答案 5 :(得分:2)
我可能只是坚持使用vector<>
,除非最小的额外开销(可能是你的实现上的一个额外的单词或指针)确实是一个问题。没有什么可以说你必须在构造一个矢量后重新调整()。
然而,使用vector<>
:
如果您确实希望阻止数组在构造后增长,您可能需要考虑让自己的类私有地从vector<>
继承或具有vector<>
成员,并且仅通过方法公开你希望客户能够使用那些向量方法的向量方法。这应该有助于让你快速行动,很好地保证泄漏和不存在的东西。如果你这样做并发现vector的小开销不适合你,你可以在没有vector的帮助下重新实现该类,你的客户端代码不需要改变。
答案 6 :(得分:1)
这里提到了许多好的想法。但是缺少一个。 Flexible Arrays是C99的一部分,因此不是C ++的一部分,尽管某些C ++编译器可能提供此功能,但不能保证这一点。如果你找到一种以可接受的方式在C ++中使用它们的方法,但是你有一个不支持它的编译器,你可能会回退到"classical" way
答案 7 :(得分:1)
你可能想要一些比矢量更轻的东西以获得高性能。您还希望非常具体地了解跨平台的数据包大小。但你也不想打扰内存泄漏。
幸运的是,升级库完成了大部分难题:
struct packet
{
boost::uint32_t _size;
boost::scoped_array<unsigned char> _data;
packet() : _size(0) {}
explicit packet(packet boost::uint32_t s) : _size(s), _data(new unsigned char [s]) {}
explicit packet(const void * const d, boost::uint32_t s) : _size(s), _data(new unsigned char [s])
{
std::memcpy(_data, static_cast<const unsigned char * const>(d), _size);
}
};
typedef boost::shared_ptr<packet> packet_ptr;
packet_ptr build_packet(const void const * data, boost::uint32_t s)
{
return packet_ptr(new packet(data, s));
}
答案 8 :(得分:0)
你应该声明一个指针,而不是一个长度未指定的数组。
答案 9 :(得分:0)
答案 10 :(得分:0)
免责声明:我写了一个小型图书馆来探索这个概念:https://github.com/ppetr/refcounted-var-sized-class
我们希望为 T
类型的数据结构和 A
类型的元素数组分配单个内存块。在大多数情况下,A
只是 char
。
为此,让我们定义一个 RAII 类来分配和释放这样的内存块。这带来了几个困难:
char
并将结构放在块中。为此,std::aligned_storage
会有所帮助。alignof(T) - 1
字节,然后使用 std::align
。// Owns a block of memory large enough to store a properly aligned instance of
// `T` and additional `size` number of elements of type `A`.
template <typename T, typename A = char>
class Placement {
public:
// Allocates memory for a properly aligned instance of `T`, plus additional
// array of `size` elements of `A`.
explicit Placement(size_t size)
: size_(size),
allocation_(std::allocator<char>().allocate(AllocatedBytes())) {
static_assert(std::is_trivial<Placeholder>::value);
}
Placement(Placement const&) = delete;
Placement(Placement&& other) {
allocation_ = other.allocation_;
size_ = other.size_;
other.allocation_ = nullptr;
}
~Placement() {
if (allocation_) {
std::allocator<char>().deallocate(allocation_, AllocatedBytes());
}
}
// Returns a pointer to an uninitialized memory area available for an
// instance of `T`.
T* Node() const { return reinterpret_cast<T*>(&AsPlaceholder()->node); }
// Returns a pointer to an uninitialized memory area available for
// holding `size` (specified in the constructor) elements of `A`.
A* Array() const { return reinterpret_cast<A*>(&AsPlaceholder()->array); }
size_t Size() { return size_; }
private:
// Holds a properly aligned instance of `T` and an array of length 1 of `A`.
struct Placeholder {
typename std::aligned_storage<sizeof(T), alignof(T)>::type node;
// The array type must be the last one in the struct.
typename std::aligned_storage<sizeof(A[1]), alignof(A[1])>::type array;
};
Placeholder* AsPlaceholder() const {
void* ptr = allocation_;
size_t space = sizeof(Placeholder) + alignof(Placeholder) - 1;
ptr = std::align(alignof(Placeholder), sizeof(Placeholder), ptr, space);
assert(ptr != nullptr);
return reinterpret_cast<Placeholder*>(ptr);
}
size_t AllocatedBytes() {
// We might need to shift the placement of for up to `alignof(Placeholder) - 1` bytes.
// Therefore allocate this many additional bytes.
return sizeof(Placeholder) + alignof(Placeholder) - 1 +
(size_ - 1) * sizeof(A);
}
size_t size_;
char* allocation_;
};
一旦我们处理了内存分配的问题,我们就可以定义一个包装类,它在分配的内存块中初始化 T
和一个 A
数组。
template <typename T, typename A = char,
typename std::enable_if<!std::is_destructible<A>{} ||
std::is_trivially_destructible<A>{},
bool>::type = true>
class VarSized {
public:
// Initializes an instance of `T` with an array of `A` in a memory block
// provided by `placement`. Callings a constructor of `T`, providing a
// pointer to `A*` and its length as the first two arguments, and then
// passing `args` as additional arguments.
template <typename... Arg>
VarSized(Placement<T, A> placement, Arg&&... args)
: placement_(std::move(placement)) {
auto [aligned, array] = placement_.Addresses();
array = new (array) char[placement_.Size()];
new (aligned) T(array, placement_.Size(), std::forward<Arg>(args)...);
}
// Same as above, with initializing a `Placement` for `size` elements of `A`.
template <typename... Arg>
VarSized(size_t size, Arg&&... args)
: VarSized(Placement<T, A>(size), std::forward<Arg>(args)...) {}
~VarSized() { std::move(*this).Delete(); }
// Destroys this instance and returns the `Placement`, which can then be
// reused or destroyed as well (deallocating the memory block).
Placement<T, A> Delete() && {
// By moving out `placement_` before invoking `~T()` we ensure that it's
// destroyed even if `~T()` throws an exception.
Placement<T, A> placement(std::move(placement_));
(*this)->~T();
return placement;
}
T& operator*() const { return *placement_.Node(); }
const T* operator->() const { return &**this; }
private:
Placement<T, A> placement_;
};
这种类型是可移动的,但显然不可复制。我们可以提供一个函数将其转换为带有自定义删除器的 shared_ptr
。但这需要在内部为引用计数器分配另一小块内存(另请参阅 How is the std::tr1::shared_ptr implemented?)。
这可以通过引入一种专门的数据类型来解决,该数据类型将在单个结构中保存我们的 Placement
、一个引用计数器和一个具有实际数据类型的字段。有关详细信息,请参阅我的 refcount_struct.h。