为什么不能对带有析构函数的类进行忆及
Why can't classes with a destructor be memcpy'ed
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容器。(
- std::unordered_map析构函数不释放内存?
- 为什么析构函数不接受C++中的参数?
- 为什么虚拟类的析构函数不会自动添加到 vtable 中?
- 为什么缺少虚拟析构函数不会导致内存泄漏?
- 在特殊情况下使析构函数不是虚拟的,并删除基指针是否安全
- 析构函数不在控制台上打印行
- 为什么析构函数不释放数组内存?
- 为什么析构函数不修改返回的变量
- 基类析构函数不是虚拟的,子类析构函数是虚拟的,程序崩溃
- unique_ptr类型擦除析构函数不能正常工作(带有警告)
- 为什么析构函数不会在您要返回的对象上调用?
- 为什么我的输出流seg出错,而我的虚拟析构函数不起作用,但当我杀死虚拟的时候,它起作用了
- 为什么析构函数不能是模板
- std::list<std::future>析构函数不阻塞
- 使对象的析构函数不调用成员的析构函数?
- 手动调用析构函数不会作为引用变量计算
- 为什么QWidget的析构函数不是虚拟的?
- 我的虚析构函数不会在抽象类中调用
- 节点.js本机插件 - 包装类的析构函数不运行
- 为什么析构函数不是默认的虚函数[c++]