Утечка памяти в BSTR в wstring

Рассмотрим следующий код. Есть ли в этом утечка памяти?

HRESULT GetPath(wstring &outDomainPath)
{
    CComBSTR bstrDomainPath;

    AnotherGetPath(&bstrDomainPath);
    outDomainPath = bstrDomainPath.Detach();
}

В чем разница? (Все еще утечка памяти??)

HRESULT GetPath(wstring &outDomainPath)
{
    CComBSTR bstrDomainPath;

    AnotherGetPath(&bstrDomainPath);
    outDomainPath = bstrDomainPath;//removed detach
}

person snb    schedule 06.02.2018    source источник


Ответы (1)


CComBSTR — это оболочка RAII для необработанных BSTR строк.

В первом фрагменте кода, который вы разместили:

outDomainPath = bstrDomainPath.Detach();

вы вызываете CComBSTR::Detach, который освобождает право собственности обернутой строки BSTR и передает это владение кому-то другому.
В вашем случае кто-то еще является объектом std::wstring (на который ссылается outDomainPath), который не обертка RAII вокруг BSTRs.

Теперь учтите, что с точки зрения системы типов C++ BSTR в основном представляет собой wchar_t*. Более того, std::wstring реализует перегрузки конструктора и оператора присваивания, которые принимают const wchar_t* в качестве входных данных, предполагая, что эти необработанные указатели wchar_t являются строками с нулевым завершением в стиле C в стиле C. Таким образом, вы можете создавать std::wstring объектов из необработанных строк в стиле C.

Проблема в том, что когда вы вызываете CComBSTR::Detach, необработанное право владения BSTR передается вызывающей стороне, которая отвечает за надлежащее освобождение строки BSTR.

Но в вашем случае вы создаете объект wstring из wchar_t* (BSTR), возвращенного CComBSTR::Detach, осиротевшим возвращенную строку BSTR.

Фактически, кто будет нести ответственность за надлежащий выпуск BSTR, вызывая SysFreeString? Деструктор std::wstring этого не сделает.

Итак, в этом случае вы пропускаете BSTR, возвращаемый CComBSTR::Detach.


С другой стороны, во втором фрагменте кода:

outDomainPath = bstrDomainPath;//removed detach

что происходит, так это то, что компилятор неявно вызывает CComBSTR::operator BSTR, что в основном дает вызывающему доступ к BSTR, обернутому внутри CComBSTR. Обратите внимание, что в этом случае не происходит передачи права собственности: вы просто наблюдаете BSTR, который по-прежнему принадлежит RAII-оболочке CComBSTR.

std::wstring имеет конструктор и перегрузку op= для создания wstring объектов из необработанных const wchar_t* строк в стиле C, заканчивающихся NUL. BSTR в основном представляет собой wchar_t* с точки зрения системы типов C++, поэтому, если ваш BSTR содержит строку, заканчивающуюся NUL (без встроенных NUL внутри), эта строка кода будет дублировать BSTR строку, скопировав ее в объект std::wstring.

Затем, в конце вашей функции GetPath, локальный объект CComBSTR bstrDomainPath выйдет из области видимости, деструктор CComBSTR будет автоматически вызван компилятором C++, а SysFreeString будет вызван для правильного освобождения обернутого BSTR.

В этом случае у вас нет утечки BSTR.

Обратите внимание, что этот код работает, если у вас нет встроенных значений NUL в BSTR. В случае встроенных значений NUL будет скопирована только первая подстрока, поскольку конструктор wstring, принимающий const wchar_t* в качестве входных данных, остановится на первом значении NUL.

Кроме того, я рекомендую вам прочитать этот интересный пост в блоге:

Руководство Эрика Липперта по семантике BSTR

person Mr.C64    schedule 06.02.2018
comment
Отлично подходит для упоминания о том, что наивное преобразование из BSTR в wchar_t* работает только в том случае, если вы можете гарантировать, что исходная строка не содержит встроенных нулевых символов. И BSTR, и wstring могут обрабатывать встроенные нули, но промежуточный wchar_t* не может. - person Euro Micelli; 07.02.2018