Это продолжение вопроса, который был задан здесь: Могут ли C++11 unique_ptr иshared_ptr преобразовываться в тип друг друга? и точнее ответ, получивший наибольшее количество голосов.
Мое внимание привлек вот этот фрагмент:
Это ключевая часть того, почему std::unique_ptr так хорошо подходит в качестве возвращаемого типа фабричной функции. Фабричные функции не могут знать, захотят ли вызывающие программы использовать семантику исключительного владения для возвращаемого объекта или же совместное владение (т. е. std::shared_ptr) будет более подходящим. Возвращая std::unique_ptr, фабрики предоставляют вызывающим сторонам наиболее эффективный интеллектуальный указатель, но не мешают вызывающим сторонам заменить его более гибким родственным указателем.
А потом я увидел повторяющийся вопрос разрешено ли присвоение unique_ptr значениюshared_ptr? у которого был комментарий:
Почему не должно быть? Это даже безопаснее, чем присваивать обычный указатель... [подробнее здесь].
Однако пользователь не уточнил, почему это безопаснее, чем назначение обычного указателя. Мне интересно, в каких случаях было бы лучше назначить unique_ptr дляshared_ptr? Если я правильно понял первый цитируемый раздел, лучше всего использовать такие случаи: (Предположим, что у нас есть рабочий базовый класс Car и все CarType наследуются от Car.)
enum class CarType
{
Sedan,
SUV,
Crossover,
Truck,
Bus
};
class MyRental
{
public:
MyRental();
void assignRentalCar(CarType type);
void doDriveAction();
void doTurnAction();
void doParkAction();
private:
std::shared_ptr<Car> currentCar;
std::shared_ptr<Bus> bus; // a Oversized Vehicle Bus : (public OversizedCar : public Car)
};
Предположим, что выше в наших частных членах у нас есть особый случай, когда переменная-член bus наследует от класса OversizeCar, который наследует от класса Car.
MyRental::MyRental() : currentCar(std::make_unique<Sedan>()),
bus(std::make_unique<Bus>())
{}
void MyRental::assignRentalCar(CarType type)
{
switch(type)
{
case CarType::SUV: currentCar = std::make_unique<SUV>(); break;
case CarType::Crossover: currentCar = std::make_unique<Crossover>(); break;
case CarType::Truck: currentCar = std::make_unique<Truck>(); break;
case CarType::Bus: currentCar = bus; break; // allowed because currentCar is a shared_ptr
case CarType::Sedan:
case default: currentCar = std::make_unique<Sedan>(); break;
}
}
void MyRental::doDriveAction()
{
currentCar->handleDrive();
}
void MyRental::doTurnAction()
{
currentCar->handleTurn();
}
void MyRental::doParkAction()
{
currentCar->handlePark();
}
Уместно ли в этом случае присвоить unique_ptrshared_ptr? И если да, то почему это уместно? Если это неуместно и лучше вместо этого присвоить shared_ptr, то когда лучше всего присвоить unique_ptrshared_ptr?
Это «безопаснее», потому что политика владения очень ясна. Принимая во внимание, Foo* p = q; владеет ли p Фу сейчас и несет ли ответственность за его уничтожение? Или q все еще владеет Фу? Foo* p = std::exchange(q, nullptr); было бы более понятно, но большая часть кода, использующего необработанные указатели, просто выполняет Foo* p = q; образом, и оставляет выяснение принадлежности в качестве упражнения для читателя.
У меня здесь два разных ответа, ха-ха.
Мой ответ был общим случаем. Я вижу случай, когда фабрика возвращает вам unique_pointer (никогда необработанный указатель). И затем вы решаете поделиться этим правом собственности с другими частями кода, но я все равно смоделировал бы это по-другому (например, переместил уникальный указатель на другой класс, которым вы затем можете поделиться).
Итак, как всегда, вам нужно заранее спроектировать эти вещи, а не просто заменять одно на другое, потому что в данный момент это кажется удобным.
«Возвращая std::unique_ptr, фабрики предоставляют вызывающим абонентам наиболее эффективный интеллектуальный указатель, но не мешают вызывающим абонентам заменить его более гибким аналогом». - Если вызывающий абонент хочет совместного владения, создание std::shared_ptr через std::make_shared() более эффективно, чем создание std::shared_ptr из std::unique_ptr. Использование std::make_shared() требует только 1 выделения памяти, тогда как перенос std::unique_ptr в std::shared_ptr предполагает 2 выделения памяти.
Предположим, я владею чем-то, а затем говорю: «Я откажусь от владения этой вещью, пока она будет принадлежать всем, кто того пожелает». Я знаю, что есть те, кто скорее умрет, чем подчинится такому коммунизму, но действительно ли вы считаете это небезопасным? Действительно, это «безопаснее», чем отдавать право собственности на то, в чем я не могу быть абсолютно уверен, что это мое.
«Однако пользователь не уточнил, почему это безопаснее, чем назначение обычного указателя». Вероятно, потому, что (это был просто комментарий) причины соответствуют причинам, по которым std::unique_ptr безопаснее, чем владение необработанным указателем. То есть причины мало (если вообще имеют) отношения к std::shared_ptr.
Ваш код не очень хорош (или является хорошей иллюстрацией для изучения ваших собственных вопросов об уникальных и общих точках), потому что фабричная функция не учитывается для использования независимо от класса MyRental. Как правило, вы хотите, чтобы фабрику мог использовать любой, кто интересуется объектами Car, даже если они не используют объекты MyRental. Учебный подход заключается в том, чтобы автономная или static-членная фабричная функция возвращала std::unique_ptr<Car>, поэтому те, кто хочет уникальное владение, уже имеют его, а те, кто хочет совместное использование, могут выполнить преобразование без дополнительных затрат, чем из необработанного указателя.
Однако, как отмечает Реми, общие указатели, как известно, более эффективно создаются с помощью make_shared<T>(...), поскольку он может выполнить одно выделение памяти для блока управления и первого общего указателя. По этой причине, если вы действительно заботитесь о поддержке оптимальной производительности, вы можете иметь отдельные фабричные функции — в идеале совместно использовать реализацию и использовать аргумент шаблона для определения типа возвращаемого значения и используемой функции.





Мне интересно, в каких случаях было бы лучше назначить unique_ptr дляshared_ptr?
Вы слишком много думаете об этом, но я полагаю, это правильный вопрос. Сценарии, в которых назначение является разумным, — это когда выполняются следующие условия.
u типа U, которое нужно назначить.s типа S, которому нужно назначить.s = u синтаксически допустимо и полезно для вашего кода.S s(u) не является разумным вариантом. Если вы можете разумно использовать конструкцию вместо присваивания, сделайте это.Эти критерии хороши для многих типов, в том числе когда U есть std::unique_ptr&& и S есть std::shared_ptr. Единственное предостережение в случае интеллектуального указателя заключается в том, что присваивание должно происходить из значения r. То есть, если unique_ptr не возвращается функцией, вам, вероятно, придется переместить его, как в случае с s = std::move(u). Обратите внимание, что это назначение перемещения приведет к тому, что u будет нулевым; у вас не будет s и u одновременно указывать на один и тот же объект.
По сути, используйте присваивание, когда оно естественно, и избегайте его, когда это не так.
Однако вы спросили о «лучшем», а не о «хорошем». Это приводит к еще одному соображении, также не касающемуся интеллектуальных указателей: избегайте ненужных преобразований. Присвоение unique_ptr<Truck>shared_ptr<Car> предполагает более сложное преобразование, чем преобразование shared_ptr<Truck> в shared_ptr<Car>. В вашем примере вы можете легко упростить, заменив std::make_unique на std::make_shared. Будьте проще и не заставляйте других спрашивать: «конвертируется ли unique_ptr в общий_ptr?»
В общем, придерживайтесь shared_ptr, когда это возможно.
TLDR: Никогда. Общий указатель моделирует совместное владение, а уникальный указатель – уникальное владение. Это две разные концепции (преобразование даже не должно было поддерживаться C++)