免责声明:我知道这是一个糟糕的设计,我只是出于好奇而问这个问题,以便尝试深入了解析构函数在C ++中是如何工作的。
在C#中,可以在类的析构函数中编写GC.KeepAlive(this)
(参见下面的编辑),这意味着即使在对象中,该对象仍将在内存中存活析构函数调用已完成。
C ++的设计是否允许从析构函数中恢复一个类似于上述C#算法的对象?
修改:正如下面的答案所指出的,GC.ReRegisterForFinalize()
与GC.KeepAlive(this)
的问题关联得更紧密。
答案 0 :(得分:100)
简短的回答是:不。 C ++不使用垃圾收集,如Java或C#。当一个物体被摧毁时,它会立即被摧毁。过得好。加入合唱团看不见。渴望峡湾等...
并且用不同的词语多次说出这一点,以便不可能轻易地重新解释......
析构函数作为对象销毁的一部分被调用。对象破坏包括调用析构函数和释放用于对象本身的内存。它是一个单一的过程,而不是两个独立的过程。析构函数在运行时,对象仍然存在,供析构函数使用,但它存在于借来的时间。一旦析构函数返回,对象就会被蒸发,这已成定局。一旦调用析构函数,对象就会被破坏,没有什么能改变它的命运。
理解这一点:调用析构函数的原因是:对象最初在堆上分配了" new",它现在正在"删除&# 34; d。 "删除"意味着"删除",而不是"删除可能"。所以对象被删除了。或者,如果对象是在堆栈上分配的,则执行线程退出范围,因此范围中声明的所有对象都将被销毁。从技术上讲,析构函数是由于对象被销毁而被调用的。所以,对象正在被摧毁。结束。
话虽如此,C ++允许您为类实现自定义分配器。如果您愿意,可以编写自己的自定义内存分配和释放功能,以实现您想要的任何功能。虽然这些从未用于堆栈分配的对象(即局部变量)。
答案 1 :(得分:52)
您实际上是在歪曲.NET中GC.KeepAlive
的作用。不能在对象的析构函数中使用它来防止该对象被破坏 - 实际上,GC.KeepAlive()
是空的并且没有实现。
请参阅.NET源代码here。
它确保作为参数传递的对象在<{1}}的调用发生之前 之前没有进行垃圾回收。传递给KeepAlive作为参数的对象可以在调用GC.KeepAlive
后立即进行垃圾回收。由于GC.KeepAlive
没有实际的实现,这纯粹基于编译器必须维护对作为参数传递给KeepAlive
的对象的引用这一事实。也可以使用将对象作为参数的任何其他函数(未由编译器或运行时内联)。
答案 2 :(得分:8)
这是一个想法:
C* gPhoenix= nullptr;
C::~C ()
{
gPhoenix= new C (*this); // note: loses any further-derived class ("slice")
}
现在,如果所涉及的对象(基础或成员)确实具有可以执行某些操作的析构函数,那么如果您delete gPhoenix;
会遇到问题,那么根据实际尝试的内容,您将需要更复杂的机制。但是你没有任何真正的目标,只是好奇的探索,所以指出这一点就足够了。
当调用析构函数的主体时,对象仍然非常好。当你从析构函数中进行正常的成员函数调用时,它看起来非常重要和正常。
拥有该对象的内存将被回收,因此您无法就地固定。离开身体后,其他破坏会自动发生,不会受到干扰。但是,您可以在此之前复制对象。
答案 3 :(得分:6)
与already been pointed out一样,GC.KeepAlive
不会这样做。
只要.NET运行,就可以使用GC.ReRegisterForFinalize
从终结器中恢复,如果你有WeakReference
或GCHandle
跟踪重新获取,你仍然可以获得对它的引用,或者只是将this
提供给课外的东西。这样做会中止破坏。
这是在.NET 2.0 no longer relevant中检测垃圾收集的一个老技巧,但仍然有效(有点,垃圾收集现在可以是部分的,并且与其他线程并行完成)。
应该强调的是,在.NET上你使用的是终结器,它在破坏之前运行,并且可以阻止它。因此,虽然技术上正确无法在销毁后恢复对象 - in any language - 但您可以接近在.NET中描述的行为,除非使用GC.ReRegisterForFinalize
。
在C ++上,您已获得correct answer。
答案 4 :(得分:4)
用任何语言都不可能。
你的理解有点不对劲。
GC.KeepAlive
会将对象标记为垃圾收集器无法收集。这将防止垃圾收集策略破坏对象,如果对象用于垃圾收集器无法跟踪使用情况的非托管代码,则它非常有用。这并不意味着对象在破坏后存在于内存中。
一旦对象开始破坏,代码将释放资源(内存,文件处理程序,网络连接)。顺序通常是从最深的派生类返回到基类。如果中间的东西是为了防止破坏,那么就不能保证这些资源可以被重新获得并且对象将处于不一致的状态。
你更想要的是拥有一个跟踪副本和引用的std::shared_ptr
,只有在没有人需要它时才会销毁该对象。
答案 5 :(得分:3)
如果有帮助,析构函数和内存分配是不同的。
析构函数只是一个函数。你可以明确地调用它。如果它没有任何破坏性,那么再次调用它(例如当对象超出范围或被删除时)不一定是有问题的,尽管它会非常奇怪;可能在标准中有一节处理这个问题。见下面的例子。 例如,一些STL容器显式调用析构函数,因为它们分别管理对象生存期和内存分配。
通常,当自动变量超出范围时,编译器将插入代码来调用析构函数,或者使用delete销毁堆分配的对象。这种内存释放不能在析构函数内部被篡改。
您可以通过提供new运算符的其他实现来使用内存分配,也可以使用现有的new来运行,但一般的默认行为是编译器会调用析构函数,这是一个整理的机会。随后将清除某些内存的事实超出了析构函数的控制范围。
#include <iostream>
#include <iomanip>
namespace test
{
class GotNormalDestructor
{
public:
~GotNormalDestructor() { std::wcout << L"~GotNormalDestructor(). this=0x" << std::hex << this << L"\n"; }
};
class GotVirtualDestructor
{
public:
virtual ~GotVirtualDestructor() { std::wcout << L"~GotVirtualDestructor(). this=0x" << std::hex << this << L"\n"; }
};
template <typename T>
static void create_destruct_delete(wchar_t const name[])
{
std::wcout << L"create_destruct_delete<" << name << L">()\n";
{
T t;
std::wcout << L"Destructing auto " << name << L" explicitly.\n";
t.~T();
std::wcout << L"Finished destructing " << name << L" explicitly.\n";
std::wcout << name << L" going out of scope.\n";
}
std::wcout << L"Finished " << name << L" going out of scope.\n";
std::wcout << L"\n";
}
template <typename T>
static void new_destruct_delete(wchar_t const name[])
{
std::wcout << L"new_destruct_delete<" << name << L">()\n";
T *t = new T;
std::wcout << L"Destructing new " << name << L" explicitly.\n";
t->~T();
std::wcout << L"Finished destructing new " << name << L" explicitly.\n";
std::wcout << L"Deleting " << name << L".\n";
delete t;
std::wcout << L"Finished deleting " << name << L".\n";
std::wcout << L"\n";
}
static void test_destructor()
{
{
std::wcout << L"\n===auto normal destructor variable===\n";
GotNormalDestructor got_normal;
}
{
std::wcout << L"\n===auto virtual destructor variable===\n";
GotVirtualDestructor got_virtual;
}
{
std::wcout << L"\n===new variables===\n";
new_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor");
new_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor");
}
{
std::wcout << L"\n===auto variables===\n";
create_destruct_delete<GotNormalDestructor>(L"GotNormalDestructor");
create_destruct_delete<GotVirtualDestructor>(L"GotVirtualDestructor");
}
std::wcout << std::endl;
}
}
int main(int argc, char *argv[])
{
test::test_destructor();
return 0;
}
示例输出
===auto normal destructor variable===
~GotNormalDestructor(). this=0x0x23fe1f
===auto virtual destructor variable===
~GotVirtualDestructor(). this=0x0x23fe10
===new variables===
new_destruct_delete<GotNormalDestructor>()
Destructing new GotNormalDestructor explicitly.
~GotNormalDestructor(). this=0x0x526700
Finished destructing new GotNormalDestructor explicitly.
Deleting GotNormalDestructor.
~GotNormalDestructor(). this=0x0x526700
Finished deleting GotNormalDestructor.
new_destruct_delete<GotVirtualDestructor>()
Destructing new GotVirtualDestructor explicitly.
~GotVirtualDestructor(). this=0x0x526700
Finished destructing new GotVirtualDestructor explicitly.
Deleting GotVirtualDestructor.
~GotVirtualDestructor(). this=0x0x526700
Finished deleting GotVirtualDestructor.
===auto variables===
create_destruct_delete<GotNormalDestructor>()
Destructing auto GotNormalDestructor explicitly.
~GotNormalDestructor(). this=0x0x23fdcf
Finished destructing GotNormalDestructor explicitly.
GotNormalDestructor going out of scope.
~GotNormalDestructor(). this=0x0x23fdcf
Finished GotNormalDestructor going out of scope.
create_destruct_delete<GotVirtualDestructor>()
Destructing auto GotVirtualDestructor explicitly.
~GotVirtualDestructor(). this=0x0x23fdc0
Finished destructing GotVirtualDestructor explicitly.
GotVirtualDestructor going out of scope.
~GotVirtualDestructor(). this=0x0x23fdc0
Finished GotVirtualDestructor going out of scope.