Что мне интересно, так это как вернуть по значению 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));
}
Возврат по значению должен считаться значением по умолчанию. (*) Отклонение от практики по умолчанию, возвращая std::unique_ptr<Cat>
, должны требовать обоснования.
Есть три основные причины для возврата указателя:
Полиморфизм. Это лучшая причина для возвращения std::unique_ptr<Cat>
вместо Cat
: что вы на самом деле можете создавать объект типа, производного от Cat
, Если вам нужен этот полиморфизм, вам абсолютно необходимо вернуть какой-то указатель. Вот почему фабричные функции обычно возвращают указатели.
Cat
не может быть перемещен дешево или не может быть перемещен вообще. «По своей сути» неподвижные типы редки; вы обычно должны попытаться исправить Cat
сделав его дешевым Но конечно Cat
это может быть тип, принадлежащий кому-то другому, к которому нельзя добавить конструктор перемещения (или, возможно, даже конструктор копирования). В этом случае вы не можете ничего сделать, кроме как использовать unique_ptr
(и пожаловаться владельцу).
Функция может потерпеть неудачу и быть неспособной построить любой действительный Cat
, В этом случае одна возможность в любом случае возвращается по значению, но выдает исключение, если Cat
не может быть построено; другой, в C ++ 11 / C ++ 14, должен заставить функцию возвращать std::unique_ptr<Cat>
и он возвращает нулевой указатель, когда нет Cat
может быть построен. В C ++ 17, однако, вы должны начать возвращать std::optional<Cat>
вместо std::unique_ptr<Cat>
в этом случае, чтобы избежать ненужного выделения кучи.
(*) Это также относится к прохождение объекты, когда вызываемая функция нуждается в своей собственной копии значения, например., конструктор, который инициализирует член класса по одному из его аргументов. Примите объект по значению и двигайтесь.
По умолчанию возврат по значению.
Исключения из этого правила:
unique_ptr
это вернулось, а скорее shared_ptr
,Я не согласен с ответом @ Brian относительно двух исключений, которые он предлагает:
nullptr
, Отказ от верного значения — это то, для чего нужны исключения, и даже если вы хотите их избежать — я бы предложил вернуть std::optional
,В отношении управления памятью они совершенно разные.
Несомненно, в настоящее время реальное функциональное различие между этими тривиальными примерами довольно невелико, если предположить, что семантика перемещения может сделать возврат по значению дешевым (во втором примере вместо этого они отвечают за перемещение указателя). И, конечно же, оба объекта будут уничтожены в одно и то же время, если вы просто сразу отпустите все из области видимости.
Но код намного менее прост с динамическим размещением и добавляет «почему?» фактор.
Вы не можете по-настоящему рационализировать разницу, не проверив, как будет использоваться результат. после функция возвращает. Затем все типичные соображения об автоматическом и динамическом распределении памяти возвращаются в игру.
В заключение, на самом деле нет универсального, всеобъемлющего способа сказать вам, должна ли фабрика динамически распределяться или возвращаться по значению. Однако лично я предпочел бы последнее для простоты (если вы не знаете, что не можете), особенно если ваши типы объектов обычно подвижны (что, вероятно, не окажет большого влияния на саму функцию из-за RVO, но может помочь вам на месте вызова).