21 ноября 2006

Smart pointers rule

Набрел в сети на Process Explorer, побаловался с ним. Посмотрел сколько мое приложение ест памяти и т.п. Провел эксперимент - в приложении несколько раз открыл-закрыл довольно тяжелое по требованиям к памяти окно.

Ну меня это, естественно, сильно напрягло. Ведь означало это скорее всего только одно - утечки памяти. А я их жутко не люблю, что даже ввел в стандарт кодирования повсеместное использование умных указателей.

Но, спрашивается, если я везде использую умные указатели, то какого хрена получаю утечки памяти? Это напрягло меня вдвойне. Я никогда в жизни не искал утечки памяти - на C++ пишу чуть больше года, до этого писал на VBA с его COM-объектами и отсутствием заморочек на счет освобождения памяти.

Однажды я попробовал некую небольшую утилиту для поиска памяти - она встраивалась в Visual Studio и по завершении приложения выдавала список утечек. Так она мне их выдало такое немерянное количество в моем приложении, что я в это не поверил и снес ее нафиг.

После этого я зарекся при поиске утечек обойтись без утилит, а просто прописать в настройках проекта Force Includes некий хедер-файл, заменяющий malloc и free на их отладочные версии запоминающие кто сколько выделил и освободил ли потом.

Но сейчас мне совсем не хотелось с этим мучаться - я ведь просто игрался с Process Explorer. Но утечки покоя мне не давали.

В результате я просто добавил код, 2000 раз открывающий и закрывающий в приложении определенное окно и смотрел на прирост сожранной памяти в Process Explorer. Постепенно убирая функционал из кода, создающего окно, я добился того, что память перестала пожираться. Соответственно, последний убранный код был кодом, дающим утечки памяти.

Это был следующий код:
std::wstring CXMLNode::OptionalAttr
(
const std::wstring& AttrName,
const std::wstring& DefaultValue
) const
{
assert( !IsNull() );
const xmlChar* const AttrStr =
xmlGetProp( _Node, WideStringToXMLChars( AttrName ).c_str() );
if ( AttrStr != NULL )
{
return XMLCharsToWideString( AttrStr );
}
else
{
return DefaultValue;
}
}
Это был класс C++-оболочки libxml, написанный не мной, а программистом, работавшим на меня. Я конечно, порадовался, что код не мой ;) И сразу очевидно, утечку дает AttrStr = xmlGetProp(...), так как xmlGetProp() - функция из C-шной библиотеки (libxml), а там про умные указатели не знают.

Так и оказалось - в документации к xmlGetProp значилось:
Returns: the attribute value or NULL if not found. It's up to the caller to free the memory with xmlFree().
А мой программист на xmlFree() забил.

Так как xmlGetProp() постоянно использовалась, пришлось написать для нее "smart pointer", чтобы не напрягать мозги на счет xmlFree(). Что-то типа такого:
// XMLAttr helper for automaticaly memory manage
class CXMLAttr
{
public:
CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName);
bool IsValid() const;
const std::wstring& Value() const;

private:
bool _IsValid;
std::wstring _Value;
};

inline CXMLAttr::CXMLAttr(xmlNodePtr Node, const std::wstring& AttributeName)
{
std::basic_string Name;
WideStringToXMLChars(AttributeName, Name);

xmlChar* const Attribute = xmlGetProp(Node, Name.c_str());

_IsValid = (Attribute != NULL);

if (Attribute)
{
XMLCharsToWideString(Attribute, _Value);
xmlFree(Attribute);
}
}

inline bool CXMLAttr::IsValid() const
{
return _IsValid;
}

inline const std::wstring& CXMLAttr::Value() const
{
return _Value;
}


Восстановил функциональность приложения, прогнал 2000 открытий-закрытий, перерасхода памяти не обнаружил.

Комментариев нет: