在 C 中,有时会使用这种方法:tio.run
#include <stdlib.h>
#include <stdio.h>
struct smth {
int size;
int data[0];
};
int main()
{
smth *x = (smth*)malloc(sizeof (smth) + 3 * sizeof (int));
x->size = 3;
x->data[0] = 1;
x->data[1] = 2;
x->data[2] = 3;
for (int q=0; q<x->size; ++q)
printf("%d ", x->data[q]);
free(x);
}
它在 C++ 中有类似物吗?如何使用 new 分配所需大小的内存?
让我们从您的代码是无效的 C/C++ 的事实开始。可以通过将其替换
[0]
为[]
并将其更改smth
为struct smth
.结构末尾的这种未调整大小的数组称为灵活数组成员,它们在 C++ 中不存在。
但是您可以自己构建替代方案。
为此,请从类中删除数组:
struct smth {int size;};
,然后计算相对于 的“数组”的地址this
,例如reinterpret_cast<char *>(this) + sizeof(*this)
。然后对齐指针(在你的情况下,没有必要),为了可靠性,你可以通过std::launder
.我不完全确定这种地址操作是否合法(即使使用
std::launder
),但我不相信这种流行的技巧会在实践中打破,所以没关系。问题:我希望能够写
delete x;
,而不是用手来写x->~MyClass(); operator delete(x);
(operator delete()
加号替换在哪里free(&x)
)。亲爱的@ueber 找到了一个链接
P0722R1
(最终出现在 C++20 中,请参阅标准中的具体更改P0722R3
),这说明您不能只delete x;
为此类类编写 - 因为现在operator delete()
一块内存的大小是自动传入第二个参数,即 .sizeof(T)
. (他们写道,这使得制造更高效的分配器成为可能。)在您的情况下sizeof(T)
,内存块大小不匹配,您会得到 UB。P0722R1
解释解决方案:在你需要添加的类里面这会阻止类大小传递给
operator delete
,并删除 UB。但P0722R1
也解释了“未调整大小”的版本operator delete
可能比调整大小的版本慢。通过从您的类
operator delete
的字段计算它可以将内存块的大小传递给,如下所示:但它不能正确完成,因为你的类析构函数已经完成,并且尝试读取它会导致未定义的行为,即使你没有在析构函数中更改它的值。size
void operator delete(void *ptr) {::operator delete(ptr, размер);}
size
因此,在 C++20 (this
P0722R3
) 中加入了所谓的。破坏operator delete
(删除的“破坏性”版本)。它是这样使用的:
通过添加一个参数
std::destroying_delete_t
,您拒绝自动调用析构函数,并且您必须自己做(因此是“破坏性的”)。这允许您在调用析构函数之前读取类的字段,并计算从中释放的内存的大小。第一个参数的类型也从 更改void *
为MyClass *
。(有趣的是,在delete
第二个参数中带有大小的 Clang 中,它仅使用附加标志 - 进行编译-fsized-deallocation
。)剩下的只是编写一个漂亮的函数来分配所需的内存量并在其中创建一个对象。类似于以下内容:
同一个
P0722R1
有一个有趣的想法:在operator new
类中的自定义计算中执行此计算:这个bandura是这样调用的:(
new(n) A(n);
第一个n
传递给new
构造函数,第二个传递给构造函数)。这种方法有什么好处尚不清楚。同样,您需要将其包装在一个函数中,以免两次写入大小,并立即返回一个智能指针。如果您想让这样的类成为任意类型的模板,您还需要以
__STDCPP_DEFAULT_NEW_ALIGNMENT__
特殊方式处理对齐程度更高(大于 )的类型。有特殊的重载operator new
/operator delete
接受对齐 - 你需要单独搜索它们。如果我理解正确,那么我们正在讨论如何使用 new 运算符将结构的实例放置在变量内存中。换句话说,这是
new с размещением
. 我将尽可能原始地粗略地做问题中显示的内容,以便更清楚:标准 20 中的一项更改专门针对这种用例 - https://wg21.link/p0722。它给出了一个可变大小类的示例:
没有加号之类的东西。您必须使用手动内存分配以及对构造函数和析构函数的调用。我们将示例重新制作成类来演示工作。
检查
也就是说,我们为 C 类加上一个数组分配了裸内存。
构造函数是手动调用的。
在 C 类的构造函数中,我们手动调用了附加数组中的构造函数。
类析构函数是手动调用的。
当调用 C 析构函数时,它会手动调用向量的析构函数,然后清除自身。
我们放弃分配的裸内存。