Подтвердить что ты не робот

Должен ли я использовать std:: shared указатель для передачи указателя?

Предположим, что у меня есть объект, которым управляет std::unique_ptr. Другие части моего кода должны получить доступ к этому объекту. Какое правильное решение для передачи указателя? Должен ли я просто передать простой указатель на std::unique_ptr::get, или я должен использовать и передавать std::shared_ptr вместо std::unique_ptr вообще?

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

EDIT: К сожалению, я забыл упомянуть, что указатель не будет просто аргументом для вызова функции, но он будет сохранен в других объектах для создания сетевой структуры объектов. Я не предпочитаю общий указатель, потому что тогда уже не ясно, кто на самом деле владеет объектом.

4b9b3361

Ответ 1

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

Это сложный способ сказать:

Дано:

std::unique_ptr<Thing> thing_ptr;

чтобы изменить Вещь:

// declaration
void doSomethingWith(Thing& thing); 

// called like this
doSomethingWith(*thing_ptr);

использовать Thing без его модификации.

// declaration
void doSomethingWith(const Thing& thing); 

// called like this
doSomethingWith(*thing_ptr);

Единственный раз, когда вы хотели бы упомянуть unique_ptr в сигнатуре функции, было бы, если бы вы передавали право собственности:

// declaration
void takeMyThing(std::unique_ptr<Thing> p);

// call site
takeMyThing(std::move(thing_ptr));

Вам не нужно делать это:

void useMyThing(const std::unique_ptr<Thing>& p);

Причина, по которой это было бы плохой идеей, заключается в том, что если смущает логика useMyThing с понятием собственности, тем самым сужая возможности для повторного использования.

Рассмотрим:

useMyThing(const Thing& thing);

Thing x;
std::unique_ptr<Thing> thing_ptr = makeAThing();
useMyThing(x);
useMyThing(*thing_ptr);

Update:

Отмечаем обновление для вопроса - сохранение (не владеющих) ссылок на этот объект.

Один из способов сделать это - действительно сохранить указатель. Тем не менее, указатели страдают от возможности логической ошибки в том, что они могут юридически быть нулевыми. Другая проблема с указателями заключается в том, что они не играют хорошо с std:: algorithms и контейнерами, требуя специальных функций сравнения и т.п.

Существует способ std::-compliant - std::reference_wrapper<>

Итак, вместо этого:

std::vector<Thing*> my_thing_ptrs;

сделайте следующее:

std::vector<std::reference_wrapper<Thing>> my_thing_refs;

Так как std::reference_wrapper<T> определяет оператор T&, вы можете использовать объект reference_wrapped в любом выражении, которое ожидало бы T.

например:

std::unique_ptr<Thing> t1 = make_thing();
std::unique_ptr<Thing> t2 = make_thing();
std::unique_ptr<Thing> t3 = make_thing();

std::vector<std::reference_wrapper<const Thing>> thing_cache;

store_thing(*t1);
store_thing(*t2);
store_thing(*t3);

int total = 0;
for(const auto& t : thing_cache) {
  total += value_of_thing(t);
}

где:

void store_thing(const Thing& t) {
  thing_cache.push_back(std::cref(t));
}

int value_of_thing(const Thing& t) {
  return <some calculation on t>;
}

Ответ 2

Обычно вы просто передаете ссылку или обычный указатель на другие части кода, которые хотят наблюдать за объектом.

Перейдите по ссылке:

void func(const Foo& foo);

std::unique_ptr<Foo> ptr;

// allocate ptr...

if(ptr)
    func(*ptr);

Передача по необработанному указателю:

void func(const Foo* foo);

std::unique_ptr<Foo> ptr;

// allocate ptr...

func(ptr.get());

Выбор будет зависеть от необходимости передавать нулевой указатель.

Вы несете ответственность за то, чтобы наблюдатели не использовали указатель или ссылку после того, как unique_ptr был уничтожен. Если вы не можете гарантировать это, вы должны использовать shared_ptr вместо unique_ptr. Наблюдатели могут содержать weak_ptr, чтобы указать, что у них нет собственности.

РЕДАКТИРОВАТЬ: Даже если наблюдатели хотят удержать указатель или ссылку, но это делает труднее гарантировать, что он не будет использоваться после уничтожения unique_ptr.

Ответ 3

Не сосредотачивайтесь на требованиях вызывающего абонента. Что нужно передать этой функции? Если он будет использовать объект только для продолжительности вызова, используйте ссылку.

void func(const Foo& foo);

Если ему необходимо сохранить право собственности на объект за его длительность, перейдите в shared_ptr

void func(std::shared_ptr<Foo> foo);

Ответ 4

Поскольку обновление вопроса, я бы предпочел:

  • std::reference_wrapper<>, как было предложено Ричардом Ходжесом, если вы не требуете значения NULL.

    Обратите внимание, что ваш прецедент, по-видимому, требует конструктора по умолчанию, поэтому, это не работает, если у вас нет публичного статического экземпляра для использования по умолчанию.

  • некоторый пользовательский тип not_your_pointer<T> с требуемой семантикой: по умолчанию - конструктивен, можно копировать и присваивать, конвертировать из unique_ptr и не иметь права собственности. Вероятно, только стоит, если вы используете его много, но, поскольку для написания нового класса интеллектуальных указателей требуется определенная мысль.

  • если вам нужно обработать оборванные ссылки изящно, используйте std::weak_ptr и измените право собственности на shared_ptr (то есть существует только один shared_ptr: нет никакой двусмысленности в отношении прав собственности и времени жизни объекта)


Перед обновлением вопроса я предпочел:

  • указывают, что право собственности не передается путем передачи ссылки:

    void foo(T &obj); // no-one expects this to transfer ownership
    

    называется:

    foo(*ptr);
    

    вы теряете возможность передавать nullptr, хотя, если это имеет значение.

  • указывают, что право собственности не передается, явно запрещая его:

    void foo(std::unique_ptr<T> const &p) {
        // can't move or reset p to take ownership
        // still get non-const access to T
    }
    

    это ясно, но выставляет политику управления памятью вызываемой стороне.

  • передать необработанный указатель и документ, в котором не должно быть передано право собственности. Это самый слабый и самый склонный к ошибкам. Не делайте этого.

Ответ 5

Эта земля уже покрыта Herb Sutter в GotW # 91 - Параметры интеллектуального указателя. Он затрагивает и аспекты производительности, в то время как другие ответы, в то время как большие, сосредоточены на управлении памятью.

Кто-то, выше рекомендуемый прохождение умного указателя в качестве ссылки на константу, изменил свое мнение в дальнейшем редактировании. У нас был код, где это злоупотребляло - это сделало подписи "умнее", то есть более сложным, но без выгоды. Хуже всего то, что младший разработчик берет на себя - он не стал бы задавать вопросы как @Michael и "учиться", как это сделать из плохого примера. Он работает, но...

Для меня вопрос о передаче параметра интеллектуального указателя достаточно серьезен, чтобы заметно упоминаться в любой книге/статье/публикации сразу после объяснения того, что такое умные указатели.

Удивительно, что если lint-подобные программы должны отмечать передачу по ссылке const как элемент стиля.