[英]Variable Sized Struct C++
這是在 C++ 中制作可變大小結構的最佳方法嗎? 我不想使用向量,因為初始化后長度不會改變。
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;
}
編輯:重命名變量名並更改代碼以使其更正確。
關於你在做什么的一些想法:
使用 C 風格的可變長度 struct idiom 允許您為每個數據包執行一次免費存儲分配,這是如果struct Packet
包含std::vector
所需數量的一半。 如果您分配一個非常大的數量的數據包,然后進行一半的自由存儲分配/釋放操作很可能是顯著。 如果您還進行網絡訪問,那么等待網絡所花費的時間可能會更顯着。
這個結構代表一個數據包。 您是否打算從套接字直接讀/寫到struct Packet
? 如果是這樣,您可能需要考慮字節順序。 發送數據包時是否必須從主機字節順序轉換為網絡字節順序,在接收數據包時反之亦然? 如果是這樣,那么您可以在可變長度結構中對數據進行字節交換。 如果您將其轉換為使用向量,則編寫用於序列化/反序列化數據包的方法是有意義的。 這些方法會將其傳輸到/從連續緩沖區傳輸,同時考慮字節順序。
同樣,您可能需要考慮對齊和打包。
你永遠不能子類Packet
。 如果這樣做,則子類的成員變量將與數組重疊。
您可以使用Packet* p = ::operator new(size)
和::operator delete(p)
代替malloc
和free
,因為struct Packet
是一種 POD 類型,目前無法從調用其默認構造函數和析構函數中受益. 這樣做的(潛在)好處是全局operator new
使用全局 new 處理程序和/或異常處理錯誤,如果這對您很重要。
可以使可變長度結構慣用語與 new 和 delete 運算符一起使用,但效果不佳。 您可以通過實現static void* operator new(size_t size, unsigned int bitlength)
創建一個接受數組長度的自定義operator new
,但您仍然必須設置 bitlength 成員變量。 如果使用構造函數執行此操作,則可以使用稍微冗余的表達式Packet* p = new(len) Packet(len)
來分配數據包。 與使用全局operator new
和operator delete
相比,我看到的唯一好處是您的代碼的客戶端可以只調用delete p
而不是::operator delete(p)
。 只要它們被正確調用,將分配/解除分配包裝在單獨的函數中(而不是直接調用delete p
)就可以了。
如果您從不向結構中添加構造函數/析構函數、賦值運算符或虛函數,則使用 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 hack。
這是可以的(並且是 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
如果需要,您可以使用“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&);
};
我可能會堅持使用vector<>
除非最小的額外開銷(可能是您的實現上的一個額外單詞或指針)確實造成了問題。 沒有什么說你必須在構造向量后調整大小()。
但是,使用vector<>
有幾個優點:
如果您真的想防止數組在構造后增長,您可能需要考慮擁有自己的類,該類私下繼承自vector<>
或具有vector<>
成員,並且僅通過僅通過向 vector 方法傳遞這些位的方法公開您希望客戶能夠使用的矢量。 這應該有助於讓你快速前進,並很好地保證泄漏和不存在的東西。 如果您這樣做並發現 vector 的小開銷對您不起作用,您可以在沒有 vector 幫助的情況下重新實現該類,並且您的客戶端代碼不需要更改。
如果您真的在使用 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。 我已經采取了一些自由並將位長度更改為字節長度。 如果這個類代表一個網絡數據包,你會更好地處理字節而不是位(在我看來)。 數據數組是一個無符號字符數組,而不是無符號整數。 同樣,這是基於我的假設,即此類代表網絡數據包。 構造函數允許您像這樣創建數據包:
Packet p; // default packet with 256-byte data array
Packet p(1024); // packet with 1024-byte data array
當 Packet 實例超出范圍並防止內存泄漏時,會自動調用析構函數。
這里已經提到了很多好的想法。 但是少了一個。 靈活數組是 C99 的一部分,因此不是 C++ 的一部分,盡管某些 C++ 編譯器可能提供此功能,但不能保證。 如果您找到一種以可接受的方式在 C++ 中使用它們的方法,但是您的編譯器不支持它,您也許可以退回到“經典”方式
您可能想要比矢量更輕的東西以獲得高性能。 您還希望非常具體地了解跨平台數據包的大小。 但是您也不想擔心內存泄漏。
幸運的是,boost 庫完成了大部分困難的工作:
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));
}
免責聲明:我寫了一個小庫來探索這個概念: 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
。 但這需要在內部為引用計數器分配另一個小內存塊(另請參閱std::tr1::shared_ptr 是如何實現的? )。
這可以通過引入一種專門的數據類型來解決,該數據類型將在單個結構中保存我們的Placement
、一個引用計數器和一個具有實際數據類型的字段。 有關更多詳細信息,請參閱我的refcount_struct.h 。
您應該聲明一個指針,而不是一個未指定長度的數組。
對未知大小的數組使用向量並沒有任何問題,這些數組將在初始化后修復。 恕我直言,這正是矢量的用途。 一旦你初始化了它,你就可以假裝它是一個數組,它的行為應該是一樣的(包括時間行為)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.