我认为下面的代码是一种有效的方法(实际上只是一个关于这个想法的小例子):
#include <memory>
void f(float* buffer, std::size_t buffer_size_in_bytes)
{
double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];
// we have started the lifetime of the doubles.
// "d" is a new pointer pointing to the first double object in the array.
// now you can use "d" as a double buffer for your calculations
// you are not allowed to access any object through the "buffer" pointer anymore since the floats are "destroyed"
d[0] = 1.;
// do some work here on/with the doubles...
// conceptually we need to destory the doubles here... but they are trivially destructable
// now we need to start the lifetime of the floats again
new (buffer) float[10];
// here we are unsure about wether we need to update the "buffer" pointer to
// the one returned by the placement new of the floats
// if it is nessessary, we could return the new float pointer or take the input pointer
// by reference and update it directly in the function
}
int main()
{
float* floats = new float[10];
f(floats, sizeof(float) * 10);
return 0;
}
重要的是你只使用你从placement new 收到的指针。并且重要的是放置新的花车。即使是无操作构造,也需要重新开始浮点数的生命周期。
忘记 cmets 中的 std::launder 和 reinterpret_cast。新的安置将为您完成这项工作。
编辑:确保在 main 中创建缓冲区时正确对齐。
更新:
我只是想提供有关 cmets 中讨论的内容的最新信息。
- 首先提到的是我们可能需要将最初创建的浮点指针更新为重新放置新的浮点返回的指针(问题是最初的浮点指针是否仍然可以用于访问浮点,因为浮点数现在是“新”浮点数,由额外的新表达式获得)。
为此,我们可以 a) 通过引用传递浮点指针并更新它,或者 b) 从函数返回新获得的浮点指针:
一)
void f(float*& buffer, std::size_t buffer_size_in_bytes)
{
double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];
// do some work here on/with the doubles...
buffer = new (buffer) float[10];
}
b)
float* f(float* buffer, std::size_t buffer_size_in_bytes)
{
/* same as inital example... */
return new (buffer) float[10];
}
int main()
{
float* floats = new float[10];
floats = f(floats, sizeof(float) * 10);
return 0;
}
接下来要提到的更重要的是,placement-new 允许有内存开销。因此允许实现在返回的数组前面放置一些元数据。如果发生这种情况,那么天真地计算有多少双打会适合我们的记忆显然是错误的。问题是,我们不知道实现将预先为特定调用获取多少字节。但这对于调整我们知道将适合剩余存储空间的双打数量是必要的。
这里 (https://stackoverflow.com/a/8721932/3783662) 是另一个 SO 帖子,其中 Howard Hinnant 提供了一个测试 sn-p。我使用在线编译器对此进行了测试,发现对于微不足道的可破坏类型(例如双精度数),开销为 0。对于更复杂的类型(例如 std::string),开销为 8 个字节。但这可能因您的平台/编译器而异。使用 Howard 的 sn-p 预先对其进行测试。
1234563但最后——当我们访问值时——我们需要使用正确的类型以避免违反严格的别名规则。简单来说:只有在指针给定的位置确实存在指针类型的对象时才允许访问对象。那么如何让物体栩栩如生?
标准说:
https://timsong-cpp.github.io/cppwp/intro.object#1:
“当隐式更改联合的活动成员或创建临时对象时,对象由定义、new 表达式创建。”
还有一个看起来很有趣的额外部门:
https://timsong-cpp.github.io/cppwp/basic.life#1:
“如果一个对象是类或聚合类型,并且它或其子对象之一由普通默认构造函数以外的构造函数初始化,则称该对象具有非空初始化。类型 T 的对象的生命周期开始时间:
- 获得了适合类型 T 的具有正确对齐和大小的存储,并且
- 如果对象有非空初始化,则其初始化完成”
所以现在我们可能会争辩说,因为替身是微不足道的,我们是否需要采取一些行动来使微不足道的对象变得栩栩如生并改变实际的生活对象?我说是的,因为我们最初获得了浮点数的存储空间,并且通过双指针访问存储空间会违反严格的别名。所以我们需要告诉编译器实际类型已经改变。整个最后一点 3 是相当有争议的讨论。你可以形成你自己的意见。您现在掌握了所有信息。