Плохая практика для возврата unique_ptr для сырого указателя, например, семантики собственности?

Я написал статический метод factory, который возвращает новый объект Foobar, заполненный другим объектом данных. Недавно я был одержим семантикой владения и задаюсь вопросом, передаю ли я правильное сообщение, если этот метод factory возвращает unique_ptr.

class Foobar {
public:
    static unique_ptr<Foobar> factory(DataObject data);
}

Мое намерение - сообщить клиентскому коду, что у них есть указатель. Без умного указателя я просто вернул бы Foobar*. Однако я хотел бы обеспечить, чтобы эта память была удалена, чтобы избежать возможных ошибок, поэтому unique_ptr показалось подходящим решением. Если клиент хочет продлить время жизни указателя, он просто вызывает .release() после получения unique_ptr.

Foobar* myFoo = Foobar::factory(data).release();

Мой вопрос состоит из двух частей:

  • Подходит ли этот подход к правильной семантике собственности?
  • Является ли это "плохой практикой" возвращать unique_ptr вместо необработанного указателя?

Ответ 1

Возврат std::unique_ptr из метода factory является прекрасным и должен быть рекомендуемой практикой. Сообщение, которое оно передает (IMO): Теперь вы являетесь единственным владельцем этого объекта. Кроме того, для вашего удобства объект знает, как уничтожить себя.

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

Однако я не понимаю вашего комментария об освобождении указателя, чтобы продлить его срок службы. В общем, я редко вижу какую-либо причину вызова release на smartpointer, так как я считаю, что указатели всегда должны управляться какой-то структурой RAII (примерно единственная ситуация, когда я называю release), - это поместить указатель в другую управляя структурой данных, например a unique_ptr с другим делетером, после того, как я сделал что-то, чтобы гарантировать дополнительную очистку).

Поэтому клиент может (и должен) просто хранить unique_ptr где-то (например, другой unique_ptr, который был перемещен, построенный из возвращенного), если им нужен объект (или shared_ptr, если им нужно несколько копий указателя). Таким образом, код clientside должен выглядеть следующим образом:

std::unique_ptr<FooBar> myFoo = Foobar::factory(data);
//or:
std::shared_ptr<FooBar> myFoo = Foobar::factory(data);

Лично я также добавил бы typedef для возвращаемого типа указателя (в данном случае std::unique_ptr<Foobar>) и или используемого делетера (в данном случае std:: default_deleter) для вашего объекта factory. Это упростит, если позже вы решите изменить выделение своего указателя (и, следовательно, необходимо использовать другой метод для уничтожения указателя, который будет отображаться в качестве второго параметра шаблона std::unique_ptr). Поэтому я бы сделал что-то вроде этого:

class Foobar {
public:  
    typedef std::default_deleter<Foobar>     deleter;
    typedef std::unique_ptr<Foobar, deleter> unique_ptr;

    static unique_ptr factory(DataObject data);
}

Foobar::unique_ptr myFoo = Foobar::factory(data);
//or:
std::shared_ptr<Foobar> myFoo = Foobar::factory(data);

Ответ 2

A std::unique_ptr уникально владеет объектом, на который он указывает. В нем говорится: "Я владею этим объектом, и никто другой не делает".

Это именно то, что вы пытаетесь выразить: вы говорите "вызывающий эту функцию: вы теперь являетесь единственным владельцем этого объекта, делайте с ним, как вам будет угодно, ваша жизнь - ваша ответственность".

Ответ 3

Он точно передает правильную семантику и так я думаю, что все фабрики на С++ должны работать: std::unique_ptr<T> не навязывает какую-либо семантику собственности и это очень дешево.