Я бы, наверное, сделал это:
const SomeObject& SomeScope::ReturnOurObject()
{
if( ! SomeCondition )
{
throw SomeException();
}
return ourObject;
}
const SomeObject *SomeScope::ReturnOurObjectIfPermitted()
{
return SomeCondition ? &ourObject : 0;
}
И, возможно, также:
bool SomeScope::CheckMode();
return SomeCondition;
}
У абонентов есть несколько вариантов:
// 1 - "I know that I'm in the right mode"
myScope.ReturnOurObject().DoSomething();
// 2 - "I don't know whether I'm in the right mode, but I can cope either way"
if (SomeObject *myObject = myScope.ReturnOurObjectIfPermitted()) {
myObject->DoSomething();
} else {
DoSomethingElse();
}
// 2 - alternate version:
if (myScope.CheckMode()) {
SomeObject &myObject = myScope.ReturnOurObject();
myObject.DoSomething();
} else {
DoSomethingElse();
}
// 3 - "I don't know whether I'm in the right mode. If I'm not then
// I can't deal with it now, but some higher-level code can"
try {
// ... several calls deep ...
myScope.ReturnOurObject().DoSomething();
// ... several returns back ...
} catch (SomeException &e) {
DoSomethingElse();
}
Вы конструируете интерфейс функции по-разному в зависимости от предварительных условий, то есть за то, кто несет ответственность за то, чтобы она могла выполнять свою работу.
Если это обязанность вызывающего абонента, тогда ему нужна способность это гарантировать. Вы можете проверить в любом случае, просто чтобы быть в безопасности. Создание исключения, когда так называемые «предварительные условия» не выполняются, делает их больше похожими на рекомендации, чем на условия, и это может работать довольно хорошо, если вы не возражаете против накладных расходов проверки во всем мире. Обычно я не прощаюсь с предусловиями, поэтому на самом деле я могу заменить условное исключение утверждением и задокументировать, что функция не должна вызываться в неправильном режиме. Это зависит от степени контроля вызывающего абонента над режимом - очевидно, если он изменяется произвольно (например, если «SomeCondition» имеет значение «нет байта, доступного в UART»), то вам нужен совершенно другой интерфейс, если он только когда-либо изменяется когда клиентский код вызывает какую-то функцию для ее изменения.
Если вызывающий не несет ответственности за правильный режим, тогда ваш интерфейс не должен выписывать чеки, которые ваша реализация не может обналичить. В этом случае, если «ожидается», что не будет возвращаемого объекта, возврат не должен быть по ссылке. Если (а) вы не можете создать специальный объект «извините, это не сработало» для возврата, который может проверить вызывающий объект, или (б) вам удобно выдавать исключения в «ожидаемых» ситуациях. IMO (b) редко встречается в C ++. (а) обычно не лучше, чем возвращать нулевой указатель, но иногда это так. Например, возвращение пустой коллекции, означающей «нет ничего, что вам здесь разрешено видеть», может привести к некоторому очень чистому вызывающему коду, если бы DoSomethingElse()
было бы «ничего не делать». Так же как и желаемые обязанности, интерфейс зависит от ожидаемых вариантов использования.