为什么不能对带有析构函数的类进行忆及

Why can't classes with a destructor be memcpy'ed

本文关键字:析构函数 不能 为什么      更新时间:2023-10-16

C++的规则说它是合法的,可以使用memcpy复制对象或POD类型。

他们进一步说POD不能有(非平凡的(析构函数。为什么会这样,为什么仅仅添加一个析构函数就会改变类,以至于使用memcpy无法工作?

// Perfectly fine to copy using memcpy
struct data
{
    int something;
    float thing;
};
// Not allowed to copy using memcpy
int var;
struct data
{
    int something;
    float thing;
    ~data() { var = 1; }
};

为什么简单地添加析构函数就无法记住结构的数据?我无法想象这会需要以任何方式更改数据布局。

我对被告知不要这样做不感兴趣,我无意这样做……我知道我不能这样做,因为"标准是这么说的",但我想知道标准这么说的原因是什么,因为这对我来说似乎不是必要的限制,我想了解原因。

编辑人们似乎误解了我的问题。我不是在问是否使用memcpy是个好主意。我在问,如果有一个非平凡的析构函数,那么将其定为非法的原因是什么。我看不出它有什么不同,我想了解为什么存在这个限制。如果有没有析构函数,我得到的关于它是个坏主意的大多数理由都同样适用。

用外行的话来说:

为什么简单地添加析构函数就无法记住结构的数据?

这并不意味着不可能,只是违法。

我无法想象这会需要以任何方式更改数据布局。

可能不会,但这是允许的。因为这个类不再是POD(即c结构(,它现在是一个c++类。

类对POD有不同的规则。由于我们无法预测编译器将如何对它们进行编码,因此我们无法再对memcpy的结果进行推理。

非平凡析构函数通常会反转在构造函数(或其他成员函数(中执行的一些影响对象状态的非平凡操作。

memcpy()复制组成对象的位。如果构造函数的行为会给出一组不同的位,那么该对象上的析构函数将试图反转一些实际上没有发生的操作。

一个典型的例子是一个类,它的构造函数分配一些资源(例如内存(,其他成员函数确保资源保持正常状态,析构函数释放该资源。使用memcpy()复制这样的对象将复制该资源的句柄,而不是为复制的对象创建该资源的新实例[这是这样的对象的复制构造函数通常所做的]。最终,将为原始对象和复制对象调用析构函数,并且资源将被释放两次。对于内存(例如,使用C的malloc()或C++的运算符new分配(,释放两次会产生未定义的行为。对于其他资源(文件句柄、互斥对象、其他系统资源(,结果各不相同,但在必须的系统上,通常不建议两次释放单个资源。

另一个问题是,一个类可能有基类,或者有成员,它们本身具有非平凡的构造函数和析构函数。即使类本身有一个什么都不做的构造函数,销毁对象也会调用所有成员和基的析构函数。使用memcpy()复制这样的对象会以我上面描述的方式影响那些基类或成员。

只有可复制的对象才能使用memcpy进行复制。具有非平凡析构函数的类不是平凡可复制的。

举个例子,假设您的类有一个指针作为其成员之一。您可以在类的构造函数中为该指针分配空间。现在,您可以在非琐碎的析构函数中执行各种操作,比如删除您分配的空间。通过memcpy,您将一点一点地复制整个结构。因此,当调用两个实例的析构函数时,它们将试图删除同一指针。

这是因为memcpy提供了一个浅拷贝,如果您有一个非琐碎的dtor,这可能是因为您的对象是某个资源的所有者,那么浅拷贝将无法为您提供正确的拷贝语义(复制所有权(。想象一下某个结构中有一个指向内部内容的指针,当结构消失时,dtor应该(可能(释放资源,但浅拷贝会让你有一个悬空的指针。

问题通常是,当你有一个执行某些操作的析构函数时,你还应该有一个复制构造函数/赋值运算符(请查阅"规则3"(。

当您记忆时,您将跳过这些复制操作,这可能会产生一些后果。

例如,您有一个指向对象的指针,并在构造函数中删除它。然后,您还应该指定一个复制操作,以便将指针/对象也复制到那里。如果使用memcpy,则有两个指向同一实例的指针,第二次销毁会导致错误。

编译器不知道你在析构函数中做了什么,也不知道是否需要特殊行为,所以它是悲观的,不再被视为非POD类型。(即使在析构函数中什么也不做(。

当您在c++11中的类中声明析构函数时,移动赋值/移动构造函数的生成也会发生类似的事情。

当内存归类所有时会出现问题:您不应该记住拥有内存的类(即使它没有析构函数(,例如:

https://ideone.com/46gFzw

#include <iostream>
#include <memory>
#include <cstring>
struct A
{
  std::unique_ptr<int> up_myInt;
  A(int val)
  :
    up_myInt(std::make_unique<int>(val))
  {}
};
int main()
{
    A a(1);
    {
      A b(2);
      memcpy(&a, &b, sizeof(A));
      std::cout << *a.up_myInt << std::endl;
      //b gets deleted, and the memory b.up_myInt points to is gone
    }
    std::cout << *a.up_myInt << std::endl;
    return 0;
}

导致

标准输出

2
0

stderr

*** Error in `./prog': double free or corruption (fasttop): 0x08433a20 ***

b超出范围时,它所拥有的数据被删除,a指向相同的数据,因此很有趣(如果您的类基本上包含任何其他stl容器,也会发生同样的情况,所以永远不要将memcpy作为stl容器。(