Почему GC не может автоматически распорядиться членами моего класса?
Когда я создаю следующий код C++/CLI в VS2008, отображается предупреждение анализа кода CA1001.
ref class A
{
public:
A() { m_hwnd = new HWND; }
~A() { this->!A(); }
protected:
!A() { delete m_hwnd; }
HWND* m_hwnd;
};
ref class B
{
public:
B() { m_a = gcnew A(); }
protected:
A^ m_a;
};
предупреждение: CA1001: Microsoft.Design: реализовать IDisposable для "B", поскольку он создает члены следующих типов IDisposable: "A".
Чтобы устранить это предупреждение, мне нужно добавить этот код в класс B:
~B() { delete m_a; }
Но я не понимаю почему. Класс A реализует IDisposable через его деструктор (и финализатор).
Так что, безусловно, всякий раз, когда A собирает мусор, вызывается финализатор или деструктор A, освобождая его неуправляемые ресурсы.
Почему B должен добавить деструктор для вызова "delete" на своем члене A?
Будет ли GC вызывать деструктор A только в том случае, если B явно вызывает "delete m_a"?
Редактировать: кажется, это работает автоматически, если вы используете метод "синтаксический сахар" для объявления члена A, например:
ref class B
{
public:
B() { }
protected:
A m_a;
};
но это не всегда возможно.
Почему GC не достаточно умен, чтобы автоматически распоряжаться указателем управляемой ссылки A^, если на него еще никто не указал?
1 ответ
Вы должны использовать семантику стека для члена и добавить деструктор в содержащий класс. Тогда участник будет ликвидирован. См. http://msdn.microsoft.com/en-us/library/ms177197.aspx
ref class B
{
public:
B() {}
~B() {}
protected:
A m_a;
};
Член по-прежнему реф. типа и до сих пор создается в куче.
Редактировать:
Dispose в.net в лучшем случае неудачен, в C# все детерминированное поведение нарушено, и вы должны быть очень осторожны с вызовами Dispose, чтобы получить поведение, ожидаемое большинством разработчиков C++.
В семантике стека C++/cli все стало лучше. Если вы не можете их использовать, вы снова вынуждены явно вызывать dispose, который в C++/cli представлен деструктором.
Единственный способ автоматически объединять в цепочку вызовы участников - это использовать семантику стека, если члены являются обычными управляемыми указателями, такими как C#, и вам придется связывать вызовы вручную.
Многие классы могут содержать один и тот же указатель A^, нет способа узнать, какой из них должен вызывать деструктор.
Вы получаете предупреждение, потому что вы реализовали деструктор, который заставляет ваш класс реализовать IDispose. Это дает вам возможность очиститься детерминистическим способом.
Один GC может только собрать объект без ссылок и вызвать финализатор. Это далеко не детерминистично. Обратите внимание, что полагаться на то, что финализатор выполнит очистку, должно быть только защитной сеткой, так как она может быть вызвана долгое время в будущем, если вообще будет.
Я бы порекомендовал попытаться спроектировать ваш код, чтобы разрешить приведенный выше шаблон.