I'm playing around with C++/CLI, using the MSDN documentation and the ECMA standard, and Visual C++ Express 2010. What struck me was the following departure from C++:
For ref classes, both the finalizer and destructor must be written so they can be executed multiple times and on objects that have not been fully constructed.
I concocted a little example:
#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 the end of the block at #1
, the automatic variable x
dies, and the destructor is called (which in turn calls the finalizer explicitly, as is the usual idiom). This is all fine and well. But then I delete the object again through the reference r
! The output is this:
Foo()
~Foo()
!Foo()
~Foo()
!Foo()
Questions:
Is it undefined behavior, or is it entirely acceptable, to call
delete r
on line#2
?If we remove line
#2
, does it matter thatr
is still a tracking handle for an object that (in the sense of C++) no longer exists? Is it a "dangling handle"? Does its reference counting entail that there will be an attempted double deletion?I know that there isn't an actual double deletion, as the output becomes this:
Foo() ~Foo() !Foo()
However, I'm not sure whether that's a happy accident or guaranteed to be well-defined behaviour.
Under which other circumstances can the destructor of a managed object be called more than once?
Would it be OK to insert
x.~Foo();
immediately before or afterr = %x;
?
In other words, do managed objects "live forever" and can have both their destructors and their finalizers called over and over again?
In response to @Hans's demand for a non-trivial class, you may also consider this version (with destructor and finalizer made to conform to the multiple-call requirement):
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;
};