使用memset和动态数组std :: complex <double>

3

由于std::complex是一个非平凡类型,在使用GCC 8.1.1编译以下内容时需要特别注意:

complex<double>* z = new complex<double>[6];
memset(z,0,6*sizeof*z);
delete [] (z);`

产生警告

清理一个非平凡类型的对象

我的问题是,这样做实际上有潜在的危害吗?


你为什么认为你需要memset()来完成这个任务?数组应该已经被正确初始化了。而且最好使用std::vector<complex<double>> z(6);来实现你想要的效果。 - πάντα ῥεῖ
我不是这样使用它的。这只是一种引发警告的方式。 - Zdenek Prusa
请注意,在这种情况下,memset 是完全多余的。new complex<double>[6] 在数组的每个元素上调用 std::complex<double> 的默认构造函数,这相当于将实部和虚部都清零。换句话说,memset 重复了作为 new[] 调用的一部分完成的工作。 - Justin
是的,请查看我上面的评论。 - Zdenek Prusa
3个回答

5

std::memset的行为只有在它修改的指针是指向可平凡复制类型的指针时才被定义。std::complex保证是字面类型,但是据我所知,并没有保证它是可平凡复制类型,这意味着std::memset(z, 0, ...)不具备可移植性。

话虽如此,std::complex有一个数组兼容性保证,它声明了一个std::complex<T>的存储正好是连续的两个T,并且可以被重新解释为这样。这似乎表明std::memset实际上是没问题的,因为它会通过这个面向数组的访问进行访问。这也可能意味着std::complex<double> TriviallyCopyable,但我无法确定。

如果您想这样做,我建议您保险起见,并使用static_assert来确认std::complex<double>是否TriviallyCopyable

static_assert(std::is_trivially_copyable<std::complex<double>>::value);

如果这个断言成立,那么你就可以保证memset是安全的。
无论哪种情况,使用 std::fill 都是安全的:
std::fill(z, z + 6, std::complex<double>{});

下降优化到调用memset,尽管在此之前有更多的指令。我建议使用std::fill,除非您的基准测试和分析表明这些额外的指令会引起问题。


它确实针对GCC和clang进行了优化,但显然在我选择该页面上的X64 MSVC时没有进行优化。 - Johannes

1

永远不要使用memset来处理非POD类型。它们有构造函数,这是有原因的。仅仅在它们上面写一堆字节极有可能得不到所需的结果(如果确实如此,那么这些类型本身就设计得很糟糕,应该首先明确它们只是POD类型 - 或者你只是不幸地发现未定义行为在这种情况下“似乎起作用” - 当你更改优化级别、编译器或平台(或月相)时,祝你调试愉快)。

千万不要这样做。


4
有趣之处在于std::complex<T>可以合法地被解释为一个T类型的数组,其中c[0] == c.real()c[1] == c.imag()。此外,一个std::complex<T>数组可以被解释为交替实部和虚部的T类型数组。因此,这个特定的情况需要一些特别的考虑。 - Justin
根据@Justin的评论,由于这些要求,最有可能sizeof(std::complex<double>)等于sizeof(double) * 2,如果是这样的话,memset就没有问题。 - NathanOliver
@NathanOliver 不仅仅是最有可能的情况;sizeof(std::complex<double>) == sizeof(double) * 2是有保证的。 - Justin
@Jesper Juhl 好的,我只是不确定是否由于 C 兼容性要求而没有 std::complex<T> 类的异常。它实际上是可以轻松复制的,并且在 https://en.cppreference.com/w/cpp/numeric/complex 的实现注释部分定义的要求并没有留下太多破坏它的选择。或者我错了吗? - Zdenek Prusa
2
@Zdenek Prusa 一个人不应该编写特定于编译器的代码。应该编写符合标准的代码,保证在不同的编译器上能够正常工作。 - Jesper Juhl
显示剩余2条评论

0

这个问题的答案是,对于符合标准的 std::complex,在 new 之后没有必要使用 memset

new complex<double>[6] 将把复数初始化为 (0, 0),因为它调用了一个默认(非平凡)构造函数来进行零初始化。 (我认为这是一个不幸的错误。) https://en.cppreference.com/w/cpp/numeric/complex/complex

如果发布的代码只是缺少 newmemset 之间的代码示例,则 std::fill 将做正确的事情。 (部分原因是特定的标准库实现内部知道如何实现 std::complex。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接