已经有很多关于严格别名规则和类型双关的帖子,但我找不到我可以理解的关于对象数组的解释。
我的目标是拥有一个内存池非模板类,用于存储对象数组。
基本上我只需要在访问时知道实际类型:它可以被视为一个非模板向量,其迭代器将是模板。
我想到的设计提出了几个问题,所以我会尝试将它们分成几个SO问题。
我的问题(这是第二个问题,见下文)是可以放置 new (第 45 和 55 行)和相应的析构函数循环(在Deallocate()
) 在算术类型以外的其他情况下可以省略吗?
#include <cassert>
#include <iostream>
#include <type_traits>
// type that support initialisation from a single double value
using test_t = float;
// just for the sake of the example: p points to at least a sequence of 3 test_t
void load(test_t* p) {
std::cout << "starting load\n";
p[0] = static_cast<test_t>(3.14);
p[1] = static_cast<test_t>(31.4);
p[2] = static_cast<test_t>(314.);
std::cout << "ending load\n";
}
// type-punning buffer
// holds a non-typed buffer (actually a char*) that can be used to store any
// types, according to user needs
struct Buffer {
// buffer address
char* p = nullptr;
// number of stored elements
size_t n = 0;
// buffer size in bytes
size_t s = 0;
// allocates a char buffer large enough for N object of type T and
// default-construct them
// calling it on a previously allocated buffer without adequate call to
// Deallocate is UB
template <typename T>
T* DefaultAllocate(const size_t N) {
size_t RequiredSize =
sizeof(std::aligned_storage_t<sizeof(T), alignof(T)>) * N;
n = N;
T* tmp;
if (s < RequiredSize) {
if (p) {
delete[] p;
}
s = RequiredSize;
std::cout << "Requiring " << RequiredSize << " bytes of storage\n";
p = new char[s];
// placement array default construction
tmp = new (p) T[N];
// T* tmp = reinterpret_cast<T*>(p);
// // optional for arithmetic types and also for trivially
// destructible
// // types when we don't care about default values
// for (size_t i = 0; i < n; ++i) {
// new (tmp + i) T();
// }
} else {
// placement array default construction
tmp = new (p) T[N];
// T* tmp = reinterpret_cast<T*>(p);
// // optional for arithmetic types and also for trivially
// destructible
// // types when we don't care about default values
// for (size_t i = 0; i < n; ++i) {
// new (tmp + i) T();
// }
}
return tmp;
}
// deallocate objects in buffer but not the buffer itself
template <typename T>
void Deallocate() {
T* tmp = reinterpret_cast<T*>(p);
// Delete elements in reverse order of creation
// optional for default destructible types
for (size_t i = 0; i < n; ++i) {
tmp[n - 1 - i].~T();
}
n = 0;
}
~Buffer() {
if (p) {
delete[] p;
}
}
};
int main() {
constexpr std::size_t N = 3;
Buffer B;
test_t* fb = B.DefaultAllocate<test_t>(N);
load(fb);
std::cout << fb[0] << '\n';
std::cout << fb[1] << '\n';
std::cout << fb[2] << '\n';
std::cout << alignof(test_t) << '\t' << sizeof(test_t) << '\n';
B.Deallocate<test_t>();
return 0;
}
Live https://godbolt.org/z/beanfW1MK
生活更复杂 https://godbolt.org/z/cM3M5Esq6
为了清楚起见,对于算术类型,完全删除放置 new 是否安全(仅将其替换为reinterpret_cast<T*>(p)
)和析构函数循环?
是否还有其他类型也可以实现?
注意:我正在使用 C++14,但我也对在更新的标准版本中如何完成它感兴趣。
问题 1 的链接 https://stackoverflow.com/questions/76946075/type-punning-and-strict-aliasing-rule-for-array-of-objects
链接到问题3 https://stackoverflow.com/questions/76946148/type-punning-with-stdaligned-alloc-for-array-of-objects
[EDIT] 这个答案 https://stackoverflow.com/a/76947531/21691539问题 3 显示我上面的 C++14 代码片段可能未正确对齐:here is https://godbolt.org/z/M4fPPxK7x受参考答案启发而提出的更好版本。
也可以看看问题1 https://stackoverflow.com/questions/76946075/type-punning-and-strict-aliasing-rule-for-array-of-objects一些额外的材料。