Почему сборщик мусора не удаляет членов моего класса автоматически?

Когда я создаю следующий код 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?
Будет ли сборщик мусора вызывать деструктор A только в том случае, если B явно вызывает "delete m_a"?


Изменить: похоже, это работает автоматически, если вы используете метод "синтаксического сахара" для объявления члена A, например:

ref class B
{
public:
    B()   { }
protected:
    A     m_a;
};

но это не всегда возможно.

Почему GC не достаточно умен, чтобы автоматически избавиться от управляемого указателя ссылки A^, если ни у кого больше нет указателя на него?


person demoncodemonkey    schedule 12.08.2009    source источник


Ответы (1)


Вы должны использовать семантику стека для члена и добавить деструктор к содержащему классу. Затем член будет удален. См. http://msdn.microsoft.com/en-us/library/ms177197.aspx

ref class B
{
public:
    B()   {}
    ~B()  {}
protected:
    A    m_a;
};

Участник по-прежнему является рефери. type и по-прежнему создается в куче.

Редактировать:

Dispose в .net в лучшем случае неудачен, в C# все детерминированное поведение нарушено, и вы должны быть очень строгими с вызовами Dispose, чтобы получить поведение, ожидаемое большинством разработчиков C++.

В семантике стека С++/cli это лучше. Если вы не можете их использовать, вам придется явно вызывать dispose, который в c++/cli представлен деструктором.

Единственный способ автоматически связать вызовы dispose с членами через семантику стека, если члены являются обычными управляемыми указателями, такими как С#, вам придется связывать вызовы вручную.

Многие классы могут содержать один и тот же указатель A^, и невозможно узнать, какой из них должен вызывать деструктор.

Вы получаете предупреждение, потому что вы реализовали деструктор, который заставляет ваш класс реализовывать IDispose. Это дает вам возможность очистить детерминированным образом.

Только сборщик мусора может собрать объект без ссылок и вызвать финализатор. Это далеко не детерминировано. Обратите внимание, что полагаться на финализатор для выполнения очистки следует только в качестве подстраховки, так как это можно будет назвать в далеком будущем, если вообще возможно.

Я бы порекомендовал попытаться разработать код, чтобы разрешить приведенный выше шаблон.

person morechilli    schedule 12.08.2009
comment
Спасибо, и это правда, что я мог бы использовать метод синтаксического сахара - на самом деле, если я это сделаю, мне вообще не нужно реализовывать деструктор (ну, он все равно подавляет предупреждение). Но в некоторых/большинстве ситуаций вы не можете использовать метод синтаксического сахара, вы можете хранить только управляемый ссылочный указатель. Я хочу знать, почему GC нужен явный вызов для удаления управляемого ссылочного указателя, чтобы избавиться от управляемого объекта. - person demoncodemonkey; 12.08.2009
comment
Многие классы могут содержать один и тот же указатель A^, нет никакого способа узнать, какой из них должен вызывать деструктор ‹‹ Сборщик мусора должен знать, какой из них был последним, и когда последний освобождается, он должен вызвать деструктор. деструктор. Казалось бы, GC не так удивителен, как мог бы быть... - person demoncodemonkey; 13.08.2009
comment
@demoncodemonkey: две основные проблемы с тем, чтобы позволить GC выполнять всю работу: (1) после удаления последней ссылки на объект может пройти произвольное количество времени, прежде чем сборщик мусора доберется до области памяти, содержащей его. , и то, что объект так долго не очищается, может быть неприемлемым (2) сборщик мусора может знать, какие ссылки доступны, но у него нет возможности узнать, какие ссылки полезны. Предположим, например, что существует объект, который подсчитывает, сколько раз форма вызывает событие Paint. Каждый раз, когда Paint поднимается... - person supercat; 06.08.2012
comment
...объект увеличивает счетчик. Форма должна будет хранить ссылку на объект, чтобы она могла отправлять Paint события, но на самом деле форма не заботится об объекте. Если никакая ссылка на объект не сохраняется чем-то, что заботится о счетчике, объект перестанет служить какой-либо полезной цели, но GC не сможет узнать об этом. Если за время существования формы будет создано и оставлено много таких объектов, это может привести к серьезной утечке памяти. - person supercat; 06.08.2012