我正在使用 C++/CLI,使用 MSDN 文档和ECMA 标准,以及 Visual C++ Express 2010。令我印象深刻的是以下与 C++ 的不同:
对于 ref 类,必须编写终结器和析构函数,以便它们可以在尚未完全构造的对象上多次执行。
我编造了一个小例子:
#include <iostream>
ref struct Foo
{
Foo() { std::wcout << L"Foo()\n"; }
~Foo() { std::wcout << L"~Foo()\n"; this->!Foo(); }
!Foo() { std::wcout << L"!Foo()\n"; }
};
int main()
{
Foo ^ r;
{
Foo x;
r = %x;
} // #1
delete r; // #2
}
在 at 块的末尾#1
,自动变量x
消失,析构函数被调用(它反过来显式调用终结器,就像通常的习语一样)。这一切都很好。但是后来我通过引用再次删除了对象r
!输出是这样的:
Foo()
~Foo()
!Foo()
~Foo()
!Foo()
问题:
delete r
在线调用是未定义的行为,还是完全可以接受的#2
?如果我们删除 line
#2
,r
是否仍然是一个对象的跟踪句柄(在 C++ 的意义上)不再存在?它是一个“悬垂的把手”吗?它的引用计数是否意味着将尝试双重删除?我知道没有实际的双重删除,因为输出变成了这样:
Foo() ~Foo() !Foo()
但是,我不确定这是否是一个愉快的事故或保证是明确定义的行为。
在哪些其他情况下可以多次调用托管对象的析构函数?
x.~Foo();
在之前或之后立即插入可以r = %x;
吗?
换句话说,托管对象是否“永远存在”并且可以一遍又一遍地调用它们的析构函数和终结函数?
为了响应@Hans 对非平凡类的需求,您还可以考虑这个版本(使用析构函数和终结器以符合多次调用要求):
ref struct Foo
{
Foo()
: p(new int[10])
, a(gcnew cli::array<int>(10))
{
std::wcout << L"Foo()\n";
}
~Foo()
{
delete a;
a = nullptr;
std::wcout << L"~Foo()\n";
this->!Foo();
}
!Foo()
{
delete [] p;
p = nullptr;
std::wcout << L"!Foo()\n";
}
private:
int * p;
cli::array<int> ^ a;
};