使用memcpy移动非POD C ++对象是否始终调用未定义的行为?

时间:2016-03-12 07:17:48

标签: c++ undefined-behavior memcpy

具体来说,我对以下情况感兴趣:

  1. 众所周知,没有指向对象的外部指针(也没有指向其任何成员的指针)。
  2. 该对象不包含内部自引用。
  3. 保证不会调用源对象的析构函数。
  4. 在这种情况下,对象应该是memcpy-movable,即使它们具有用户定义的构造函数,析构函数或虚函数。但是,我想知道这是否仍然被认为是UB,过度热心的编译器可能会邀请我格式化我的硬盘?

    编辑:请注意我要求破坏性移动,而不是复制

    是的,我知道is_trivially_copyable和其他人。但是,is_trivially_copyable只涵盖了C ++类的一小部分,而上述情况在实践中非常普遍。

2 个答案:

答案 0 :(得分:3)

在C ++ 11之前,是的,使用memcpy()移动非POD类型会调用未定义的行为。

从C ++ 11开始,定义已经收紧,因此不一定如此。以下内容适用于C ++ 11或更高版本。

POD相当于同时兼顾"平凡" (这实际上意味着"可以静态初始化")和"标准布局" (这意味着许多事情,包括没有虚函数,对所有非静态成员具有相同的访问控制,没有非标准布局的成员,没有与第一个非静态成员相同类型的基类,和其他一些属性)。

这是"可轻易复制的"正如Joseph Thomson在评论中指出的那样,允许使用memcpy()复制对象的属性。 A"琐事"类型是可以复制的,但反过来却不正确(例如,一个类可能有一个非平凡的默认构造函数 - 这使得它非常重要 - 但仍然可以轻易地复制)。类型也可能是微不足道的,但不是标准布局(这意味着它不是POD,因为POD类型具有普通的AND标准布局属性)。

可以使用std::is_trivial<type>或(复制)std::is_trivially_copyable<type>来测试平凡属性。可以使用std::is_standard_layout<type>测试标准布局属性。这些是在标准标题<type_traits>中声明的。

答案 1 :(得分:-3)

这里没有任何未定义的内容。如果有虚函数,那么vtable也会被复制。不是一个好主意,但如果类型相同,它将起作用。

问题是你需要知道课堂上所有内容的细节。即使没有指针,也许构造函数分配了一个唯一的id,或者其他一千个不能复制的东西。使用memcpy就像告诉编译器你确切知道你在做什么。确保就是这种情况。

编辑:“未在C ++标准中定义”与“可能使用我正在使用的编译器格式化硬盘”之间存在大量可能的插入。一些澄清如下。

经典未定义行为

以下是每个人可能同意的行为示例:

void do_something_undefined()
{
    int i;
    printf("%d",i);
}

未由C ++标准定义

您可以使用不同的,更严格的undefined定义。拿这个代码片段:

struct MyStruct
{
    int a;
    int b;
    MyStruct() : a(1),b(2)
    {
    }
    ~MyStruct()
    {
        std::cout << "Test: Deleting MyStruct" << std::endl;
    }
};

void not_defined_by_standard()
{
    MyStruct x,y;
    x.a = 5;
    memcpy(&y, &x, sizeof(MyStruct)); // or std::memcpy
}

在标准参考文献中使用前面的海报,memcpy的这种用法不是由C ++标准定义的。从理论上讲,C ++标准可能会为每个非平凡的破坏类添加一个唯一的ID,导致x和y的析构函数失败。即使标准允许这样做,您也可以知道,对于您的特定编译器,它是否执行此操作。

我会在这里产生语义差异并将其称为“未定义”而不是“未定义”。一个问题是类似律师的术语定义:C ++标准中的“未定义行为”意味着“未在标准中定义”,而不是“在使用特定编译器时给出未定义的结果”。虽然标准可能没有定义它,但您完全可以知道它是否与您的特定编译器未定义。 (请注意,std :: memcpy的cppreference说“如果对象不是TriviallyCopyable,则memcpy的行为未指定且可能未定义”。这表示memcpy是未指定的行为而不是未定义的行为,这是我的全部观点。)

因此,您需要确切地知道自己在做什么。如果您正在编写需要存活多年的可移植代码,不要这样做

为什么C ++标准不喜欢这段代码?

简单:上面的memcpy调用有效地破坏并重新构造y。它在不调用析构函数的情况下完成此操作。 C ++标准根本就不喜欢这个。