Лучший способ вернуться рано из функции, возвращающей ссылку - PullRequest
8 голосов
/ 18 ноября 2009

Допустим, у нас есть функция вида:

const SomeObject& SomeScope::ReturnOurObject()
{
    if( ! SomeCondition )
    {
        // return early
        return  ;
    }

    return ourObject;
}

Ясно, что в приведенном выше коде есть проблема: если условие не выполняется, у нас возникает проблема с тем, как вернуться из этой функции. Суть моего вопроса в том, как лучше всего справиться с такой ситуацией?

Ответы [ 14 ]

24 голосов
/ 18 ноября 2009

Это не проблема синтаксиса, а проблема дизайна. Вы должны указать, что ReturnOurObject() должно возвращать, когда SomeCondition истинно. Это зависит главным образом от того, для чего будет использоваться функция. И что ты не сказал нам.

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

  • вернуть ссылку на какой-то другой объект; Вы должны иметь где-нибудь объект эрзаца
  • где-то есть специальный объект "без объекта для возврата", на который вы возвращаете ссылку; клиенты могут проверить это; если они не проверяют, они получают разумное поведение по умолчанию
  • вернуть указатель, а не ссылку; клиенты должны всегда проверять возвращаемое значение функции
  • бросить исключение; если SomeCondition является чем-то исключительным, с чем клиенты не могут справиться, это будет уместно
  • утверждают; если SomeCondition всегда должно выполняться, то должно быть заявлено
13 голосов
/ 18 ноября 2009

Я бы использовал указатель вместо ссылки в этом случае. На самом деле этот критерий (необязательное или обязательное возвращаемое значение) - это то, как я выбираю между указателями и ссылками в первую очередь.

6 голосов
/ 18 ноября 2009

Я бы, наверное, сделал это:

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() было бы «ничего не делать». Так же как и желаемые обязанности, интерфейс зависит от ожидаемых вариантов использования.

5 голосов
/ 18 ноября 2009

Я бы вернул логическое значение и передал бы ссылку на объект в качестве параметра функции:

bool getObject(SomeObject& object)
{
    if( condition )
         return false;

    object = ourObject;
    return true;
}

В таком случае вы теперь ваш объект были заполнены, только если функция возвращает true.

5 голосов
/ 18 ноября 2009

Каков срок действия нашего объекта и где он создан?

Если вам нужно вернуться рано, не трогая / не обновляя ourObject, и он существует в точке, в которую вы возвращаетесь, я не вижу проблем с возвратом ссылки на него.

Если вы пытаетесь сообщить, что произошла ошибка, вам, вероятно, придется выдать исключение, а не возвращать рано, потому что контракт, на который ваша функция согласилась, говорит, что она возвратит ссылку на SomeObject.

Если вы возвращаете ссылку на временный файл, вам лучше исправить , что проблема ...

5 голосов
/ 18 ноября 2009

Если вы не хотите менять тип возвращаемого значения на указатель, вы можете использовать шаблон нулевого объекта

3 голосов
/ 18 ноября 2009

Брось исключение

3 голосов
/ 18 ноября 2009

Э-э ... просто выбросить исключение ...

1 голос
/ 19 ноября 2009

Ровно три варианта ...

1. Бросить исключение 2. Возвращает фиктивный объект ошибки 3. Использовать указатель и вернуть NULL

1 голос
/ 18 ноября 2009

Если вы не хотите выдавать исключение, вы можете попробовать что-то вроде:

const SomeObject& GetSomeObject(const SomeObject& default_if_not_found = SomeObject())
{
    // ...
    if (!found)
        return default_if_not_found;

    return ourObject;
}

Это означает, что вам не нужно иметь какую-то статическую переменную, лежащую где-то, чтобы вернуться, и вы все равно можете вызывать ее без параметров. Я предполагаю, что вы предполагаете, что созданное по умолчанию состояние SomeObject () является пустым состоянием, поэтому вы можете проверить SomeObject, чтобы убедиться, что оно находится в пустом состоянии. Также иногда полезно использовать это, чтобы переопределить возвращаемое значение по умолчанию, например. GetSomeObject (SomeObject («Я пустой»))

...