Когда я должен возвращать по значению, а не возвращать уникальный указатель

Что мне интересно, так это как вернуть по значению Cat на самом деле отличаются от возвращения std::unique_ptr<Cat> с точки зрения их передачи, управления памятью и использования их на практике.

Мудрое управление памятью, разве они не одинаковы? Как и объекту, возвращаемому по значению, и объекту, заключенному в unique_ptr, будут срабатывать деструкторы, как только они выходят из области видимости?

Итак, как бы вы сравнили обе части кода:

Cat catFactory(string catName) {
return Cat(catName);
}

std::unique_ptr<Cat> catFactory(string catName) {
return std::unique_ptr(new Cat(catName));
}

3

Решение

Возврат по значению должен считаться значением по умолчанию. (*) Отклонение от практики по умолчанию, возвращая std::unique_ptr<Cat>, должны требовать обоснования.

Есть три основные причины для возврата указателя:

  1. Полиморфизм. Это лучшая причина для возвращения std::unique_ptr<Cat> вместо Cat: что вы на самом деле можете создавать объект типа, производного от Cat, Если вам нужен этот полиморфизм, вам абсолютно необходимо вернуть какой-то указатель. Вот почему фабричные функции обычно возвращают указатели.

  2. Cat не может быть перемещен дешево или не может быть перемещен вообще. «По своей сути» неподвижные типы редки; вы обычно должны попытаться исправить Cat сделав его дешевым Но конечно Cat это может быть тип, принадлежащий кому-то другому, к которому нельзя добавить конструктор перемещения (или, возможно, даже конструктор копирования). В этом случае вы не можете ничего сделать, кроме как использовать unique_ptr (и пожаловаться владельцу).

  3. Функция может потерпеть неудачу и быть неспособной построить любой действительный Cat, В этом случае одна возможность в любом случае возвращается по значению, но выдает исключение, если Cat не может быть построено; другой, в C ++ 11 / C ++ 14, должен заставить функцию возвращать std::unique_ptr<Cat> и он возвращает нулевой указатель, когда нет Cat может быть построен. В C ++ 17, однако, вы должны начать возвращать std::optional<Cat> вместо std::unique_ptr<Cat> в этом случае, чтобы избежать ненужного выделения кучи.

(*) Это также относится к прохождение объекты, когда вызываемая функция нуждается в своей собственной копии значения, например., конструктор, который инициализирует член класса по одному из его аргументов. Примите объект по значению и двигайтесь.

8

Другие решения

По умолчанию возврат по значению.

Исключения из этого правила:

  1. Cat должен существовать в куче, чтобы пережить код, запускающий его создание … но в этом случае, возможно, он не должен быть unique_ptr это вернулось, а скорее shared_ptr,
  2. Вы на самом деле не создаете Кошку, а получаете доступ к чему-то, что можно интерпретировать как Кошку; в этом случае, опять же, вам, вероятно, не нужен уникальный указатель, а обычный (или уникальный указатель с пользовательским средством удаления).
  3. Полиморфизм — если это фабрика, и Cat является одним из ее продуктов, вы, вероятно, также можете сделать Собаку и Лошадь, все из которых являются Животными, поэтому вы вернете указатель на Животное. Это определенно тот случай, когда вы будете использовать уникальный указатель.
  4. Темное вуду в ваших конструкторах копирования, назначения и / или перемещения, поэтому важно всегда следить за тем, чтобы ваш кот выкалывал только издалека.

Я не согласен с ответом @ Brian относительно двух исключений, которые он предлагает:

  • Я бы предложил не использовать указатель типа возврата, чтобы иметь возможность указать сбой, возвращая nullptr, Отказ от верного значения — это то, для чего нужны исключения, и даже если вы хотите их избежать — я бы предложил вернуть std::optional,
  • Обычно вам не нужен конструктор перемещения для оптимизации возвращаемого значения, поэтому отсутствие конструктора перемещения не должно быть причиной для возврата указателя.
3

В отношении управления памятью они совершенно разные.

Несомненно, в настоящее время реальное функциональное различие между этими тривиальными примерами довольно невелико, если предположить, что семантика перемещения может сделать возврат по значению дешевым (во втором примере вместо этого они отвечают за перемещение указателя). И, конечно же, оба объекта будут уничтожены в одно и то же время, если вы просто сразу отпустите все из области видимости.

Но код намного менее прост с динамическим размещением и добавляет «почему?» фактор.

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

В заключение, на самом деле нет универсального, всеобъемлющего способа сказать вам, должна ли фабрика динамически распределяться или возвращаться по значению. Однако лично я предпочел бы последнее для простоты (если вы не знаете, что не можете), особенно если ваши типы объектов обычно подвижны (что, вероятно, не окажет большого влияния на саму функцию из-за RVO, но может помочь вам на месте вызова).

0
По вопросам рекламы [email protected]