2015-02-23 5 views
21

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

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

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

+1

Почему бы не передать ссылку объекта unique_ptr вокруг? – Zaiborg

+0

У вас не может быть 'unique_ptr' и' shared_ptr', которые управляют одним и тем же объектом. –

+3

Передача ссылки на упомянутый объект - хорошая идея. –

ответ

18

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

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

Дано:

std::unique_ptr<Thing> thing_ptr; 

изменить вещь:

// declaration 
void doSomethingWith(Thing& thing); 

// called like this 
doSomethingWith(*thing_ptr); 

использовать вещь, не изменяя его.

// 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>; 
} 
+0

Это очень хороший ответ, спасибо большое! К сожалению, я забыл указать на один важный факт, который я теперь редактировал в своем посте. – Michael

+0

@Michael Я думаю, что даже если другие части кода захотят придерживаться ссылки, этот совет все еще стоит. Пока вы можете убедиться, что другие части кода не будут использовать его после уничтожения 'unique_ptr'. Если вы не можете гарантировать, что тогда 'unique_ptr' не подходит. –

6

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

Pass по ссылке:

void func(const Foo& foo); 

std::unique_ptr<Foo> ptr; 

// allocate ptr... 

if(ptr) 
    func(*ptr); 

Pass сырым указателем:

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.

+0

Я думаю, что вы касаетесь проблемы здесь с «вашей ответственностью». Удержание ссылок на возможно умершие объекты является признаком плохой связи (структура данных должна быть проинформирована о том, что элемент удален). И действительно, слабый_ptr может быть дешевым выходом. –

3

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

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

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

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

  3. , если вам нужно обрабатывать оборванную ссылку грациозно, используйте std::weak_ptr и изменение права собственности на shared_ptr (То есть, только один shared_ptr существует: нет никакой двусмысленности собственности и объекта жизни не влияет)


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

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

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

    называется:

    foo(*ptr); 
    

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

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

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

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

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

+0

Что означает 'const unique_ptr &' предложение, что нет указателя raw? И, как вы говорите, недостаток заключается в том, что он раскрывает вашу политику управления памятью? –

+0

const-ref-to-unique_ptr делает код явно документом и гарантирует договор. Маловероятно, что собеседник придет к мысли, что они предполагаются (или разрешены), чтобы взять на себя ответственность, и им придется использовать 'const_cast' или какой-либо другой хак, чтобы получить его. И в отличие от исходной ссылки, вы все равно можете передать 'nullptr'. – Useless

+0

@ Необязательно: ничто в спецификации 'const unique_ptr &' не позволяет получить базовый необработанный указатель через 'p.get()' и делать то, что он хочет с этим (хранение в структуре данных - это то, что предлагает OP). И предположение по умолчанию при передаче необработанного указателя или ссылки будет заключаться в том, что право собственности не передается (учитывайте все эти функции, принимающие 'const char *'), поэтому вариант 2 действительно не покупает вас. Главное - очень четко документировать, что указатель не принадлежит в том месте, где он хранится. –

4

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

void func(const Foo& foo); 

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

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

Последняя часть может быть более понятной, учитывая, что вопрос начинается с «Предположим, у меня есть объект, которым управляет std :: unique_ptr». –

0

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

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

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

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

Смежные вопросы