Могу ли я привести значение shared_ptr <T>& к shared_ptr <T const> & без изменения use_count? - PullRequest
5 голосов
/ 01 апреля 2012

У меня есть программа, которая использует boost::shared_ptr s и, в частности, полагается на точность use_count для выполнения оптимизаций.

Например, представьте себе операцию добавления с двумя указателями на аргументы, называемые lhsи rhs.Скажем, они оба имеют тип shared_ptr<Node>.Когда придет время выполнить сложение, я проверю use_count, и если я обнаружу, что один из аргументов имеет счетчик ссылок ровно один, я буду использовать его повторно для выполнения операции на месте.Если ни один из аргументов не может быть повторно использован, я должен выделить новый буфер данных и выполнить операцию не на своем месте.Я имею дело с огромными структурами данных, поэтому оптимизация на месте очень полезна.

Из-за этого я никогда не могу скопировать shared_ptr s без причины, т. Е. Каждая функция принимает shared_ptrs по ссылке или константной ссылке, чтобы избежать искажения use_count.

У меня такой вопрос: у меня иногда есть shared_ptr<T> &, который я хочу привести к shared_ptr<T const> &, но как я могу это сделать, не искажаяиспользовать счет?static_pointer_cast возвращает новый объект, а не ссылку.Я был бы склонен думать, что это будет работать только для того, чтобы разыграть все shared_ptr, как в:Работа.Есть ли способ сделать это, который гарантированно безопасным и правильным?

Обновление, чтобы сфокусировать вопрос

Критика дизайна не помогает ответить на этот пост.Необходимо рассмотреть два интересных вопроса:

  1. Существует ли какая-либо гарантия (автором boost::shared_ptr или стандартом, в случае std::tr1::shared_ptr), что shared_ptr<T>и shared_ptr<T const> имеют идентичные макеты и поведение?

  2. Если (1) истинно, то является ли вышеупомянутое законным использование reinterpret_cast?Я думаю, что вам будет сложно найти компилятор, который генерирует ошибочный код для приведенного выше примера, но это не значит, что он легален.Каким бы ни был ваш ответ, можете ли вы найти поддержку для него в стандарте C ++?

Ответы [ 4 ]

10 голосов
/ 01 апреля 2012

У меня иногда есть shared_ptr<T> &, который я хочу привести к shared_ptr<T const> &, но как я могу сделать это, не искажая счет использования?

Ты не. Само понятие неверно. Посмотрите, что происходит с голым указателем T* и const T*. Когда вы разыгрываете T* в const T*, у вас теперь есть два указателя . У вас нет двух ссылок на один и тот же указатель; у вас есть два указателя.

Почему это должно отличаться для умных указателей? У вас есть два указателя: один на T, а другой на const T. Они оба являются владельцами одного и того же объекта, поэтому вы используете два из них. Следовательно, use_count должно быть 2, а не 1.

Ваша проблема - ваша попытка перегрузить значение use_count, используя его функциональность для каких-то других целей. Короче говоря: вы делаете это неправильно.

Ваше описание того, что вы делаете с shared_ptr s, кто use_count один, это ... страшно . Вы в основном говорите, что некоторые функции используют один из своих аргументов, который вызывающий абонент явно использует (поскольку вызывающий, очевидно, все еще использует его). И вызывающая сторона не знает, какая из них была заявлена ​​(если таковая имеется), поэтому вызывающая сторона не знает , не подозревая , в каком состоянии находятся аргументы после функции. Изменение аргументов для таких операций обычно не очень хорошая идея.

Кроме того, то, что вы делаете, может работать, только если вы передадите shared_ptr<T> по ссылке, что само по себе не очень хорошая идея (как и обычные указатели, умные указатели почти всегда должны приниматься по значению).

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

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

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

4 голосов
/ 01 апреля 2012

static_pointer_cast - правильный инструмент для работы - вы уже определили это.

Проблема заключается не в том, что он возвращает новый объект, а в том, что он оставляет старый объект неизменным.,Вы хотите избавиться от неконстантного указателя и двигаться дальше с помощью константного указателя.То, что вы действительно хотите, это static_pointer_cast< T const >( std::move( old_ptr ) ).Но перегрузка для ссылок rvalue отсутствует.

Обходной путь прост: вручную аннулируйте старый указатель так же, как это делает std::move.

auto my_const_pointer = static_pointer_cast< T const >( modifiable_pointer );
modifiable_pointer = nullptr;

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

Кроме того: используйте pointer.unique() вместо use_count() == 1.В некоторых реализациях может использоваться связанный список без кэшированного количества использований, что делает use_count() O (N), тогда как тест unique остается O (1).Стандарт рекомендует unique для копирования при оптимизации записи.

РЕДАКТИРОВАНИЕ: Теперь я вижу, что вы упоминаете

Я никогда не смогу скопировать shared_ptr s безпричина, т. е. каждая функция принимает shared_ptr s по ссылке или константной ссылке, чтобы избежать искажения use_count.

Это неправильно.Вы добавили еще один уровень семантики владения поверх того, что уже делает shared_ptr.Они должны передаваться по значению, при этом std::move используется там, где вызывающий абонент больше не желает владения. Если профилировщик говорит , что вы тратите время на настройку счетчиков ссылок, то вы можете добавить некоторые ссылки на указатель во внутренние циклы.Но, как правило, если вы не можете установить указатель на nullptr, потому что вы больше не используете его, но кто-то другой может это сделать, то вы действительно потеряли право собственности.

1 голос
/ 01 апреля 2012

Если вы приведете shared_ptr к другому типу без изменения количества ссылок, это означает, что теперь у вас будет два указателя на одни и те же данные. Следовательно, если вы не удалите старый указатель, вы не сможете сделать это с shared_ptr s, не «исказив счетчик ссылок».

Я бы посоветовал вам вместо этого использовать необработанные указатели, вместо того, чтобы не использовать возможности shared_ptr s. Если вам иногда нужно создавать новые ссылки, используйте enable_shared_from_this, чтобы получить новый shared_ptr для существующего необработанного указателя.

0 голосов
/ 01 апреля 2012

Когда придет время выполнить сложение, я проверю use_count, и если я обнаружу, что один из аргументов имеет счетчик ссылок ровно один, то я буду использовать его повторно для выполнения операции на месте .

Это не обязательно верно, если вы не применяете некоторые другие правила ко всей программе, чтобы сделать это так. Рассмотрим:

shared_ptr<Node> add(shared_ptr<Node> const &lhs,shared_ptr<Node> const &rhs) {
    if(lhs.use_count()==1) {
        // do whatever, reusing lhs
        return lhs;
    }
    if(rhs.use_count()==1) {
        // do whatever, reusing rhs
        return rhs;
    }
    shared_ptr<Node> new_node = ... // do whatever without reusing lhs or rhs
    return new_node;
}

void foo() {
    shared_ptr<Node> a,b;
    shared_ptr<Node> c = add(a,b);

    // error, we still have a and b, and expect that they're unchanged! they could have been modified!
}

Вместо этого, если вы берете умные указатели по значению:

shared_ptr<Node> add(shared_ptr<Node> lhs,shared_ptr<Node> rhs) {

И use_count () == 1 тогда вы знаете, что ваша копия - единственная, и ее можно безопасно использовать повторно.

Однако существует проблема в использовании этого в качестве оптимизации, поскольку копирование shared_ptr требует синхронизации. Вполне может быть, что вся эта синхронизация повсеместно стоит гораздо больше, чем вы экономите, повторно используя существующие shared_ptrs. Вся эта синхронизация является причиной, по которой рекомендуется, чтобы код, который не становится владельцем shared_ptr, брал shared_ptr по ссылке, а не по значению.

...