Один из подходов - никогда не использовать возвращаемое значение функции. Используйте только выходные параметры, как во втором случае. В любом случае это уже правило в опубликованных интерфейсах COM.
Вот "официальная" ссылка, но, как обычно, она даже не упоминает ваш первый случай: http://support.microsoft.com/kb/104138
Но внутри компонента запрещение возвращаемых значений создает уродливый код. Гораздо приятнее иметь возможность компоновки - то есть удобно объединять функции, передавая возвращаемое значение одной функции непосредственно в качестве аргумента другой.
Умные указатели позволяют вам это делать. Они запрещены в общедоступных интерфейсах COM, но также и возвращаемые значения, отличные от HRESULT. Следовательно, ваша проблема исчезнет. Если вы хотите использовать возвращаемое значение для передачи указателя интерфейса, сделайте это с помощью умного указателя. И сохраняйте членов в умных указателях.
Однако, если по какой-то причине вы не хотите использовать умные указатели (кстати, вы сумасшедшие!), Тогда я могу сказать вам, что ваши рассуждения верны. Ваша функция действует как «свойство getter», и в вашем первом примере она не должна AddRef
.
Так что ваше правило верно (хотя в вашей реализации есть ошибка, о которой я сообщу через секунду, поскольку вы, возможно, ее не заметили).
Эта функция хочет объект:
void Foo(IUnknown *obj);
Это вообще не должно влиять на refcount obj
, если только он не хочет сохранить его в переменной-члене. Разумеется, НЕ будет обязанностью Foo позвонить Release
на obj
до его возвращения! Представьте себе беспорядок, который может создать.
Теперь эта функция возвращает объект:
IUnknown *Bar();
И очень часто нам нравится составлять функции, передавая выходные данные одного непосредственно другому:
Foo(Bar());
Это не сработало бы, если бы Bar
увеличил счет возврата того, что вернул. Кто собирается Release
это? Так что Bar
не звонит AddRef
. Это означает, что он возвращает что-то, что хранит и управляет им, т. Е. Фактически он получает объект.
Также, если вызывающий абонент использует интеллектуальный указатель, p
:
p = Bar();
Любой здравомыслящий умный указатель собирается AddRef
, когда ему назначается объект. Если бы Bar
также хорошо AddRef
, мы снова пропустили один счет. Это действительно частный случай той же проблемы компоновки.
Выходные параметры (указатель-на-указатель) различаются, так как они не подвержены проблеме совместимости одинаково:
Опять же, умные указатели обеспечивают наиболее распространенный случай, используя ваш второй пример:
myClass.getObj(&p);
Интеллектуальный указатель здесь не будет выполнять никакого пересчета, поэтому getObj
должен это сделать.
Теперь мы подошли к ошибке. Предположим, что умный указатель p
уже указывает на что-то, когда вы передаете это getObj
...
Исправленная версия:
void getObj(IUnknown **outObj)
{
if (*outObj != 0)
(*outObj)->Release();
*outObj = m_obj;
(*outObj)->AddRef(); // might want to check for 0 here also
}
На практике люди совершают эту ошибку так часто, что мне проще заставить мой умный указатель утверждать, если operator&
вызывается, когда у него уже есть объект.