Был конструктор raw-указателя shared_ptr ошибкой?

Оглядываясь назад, учитывая make_shared, было бы shared_ptr есть конструктор, который принимает необработанный указатель, если он был введен в C++11?

Есть ли веские аргументы или варианты использования в пользу этого конструктора?

Это позволило бы избежать хорошо документированной ловушки исключительной безопасности и преимуществ использования памяти / производительности при использовании make_shared,

Я считаю, что еще одно преимущество требует shared_ptr строительство через make_shared было бы то, что это мог быть единственный указатель под капотом, уменьшая его использование памяти и делая вещи как atomic_compare_exchange намного проще (и возможно более эффективный). (см. презентацию из C++ сейчас)

РЕДАКТИРОВАТЬ

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

  1. возможность освободить объект отдельно от контрольного блока (что хорошо, если вы долго жили слабо)

  2. совместимость с библиотеками, которые передают вам сырые указатели и ответственность за их освобождение

  3. возможность хранить произвольные ресурсы с пользовательскими удалителями (или без них, для не владеющих указателями)

  4. способность указывать на подобъект (например, член), сохраняя при этом родительский объект живым.

Я предлагаю, чтобы эти функции не использовались достаточно часто (или в случае использования их в качестве raii-обертки), возможно, не подходят лучше всего, чтобы оправдать дополнительные расходы:

  1. отдельный указатель на блок управления
  2. (потенциально) более сложная логика atomic_compare_exchange, возможно, не стоит того.

В мире C++98 (где был представлен shared_ptr) make_shared менее практичен и менее удобен для пользователя (отсутствие совершенной пересылки требует использования упаковщиков ссылок, а отсутствие шаблонов с переменными параметрами делает реализацию неуклюжей).

3 ответа

Решение

Проблема с вашей логикой заключается в убеждении, что причина, почему shared_ptr имеет различие между управляемым указателем и get указатель потому что make_shared не было доступно И поэтому, если мы заставили всех использовать make_shared создавать shared_ptr Нам не нужно это различие.

Это неверно

Вы можете реализовать shared_ptr основанный на указателе конструктор без этого различия. Ведь при первоначальном создании управляемого shared_ptr, get Указатель и управляемый указатель совпадают. Если бы вы хотели shared_ptr быть sizeof(T*) Вы могли бы просто иметь shared_ptr принести get указатель из управляемого блока. Это независимо от того, является ли T встроен в управляемый блок.

Таким образом, различие на самом деле не имеет ничего общего с make_shared и его способность встраивать T в той же памяти, что и управляемый блок. Вернее, его отсутствие.

Нет, различие между управляемым указателем и get указатель был создан, потому что он добавил функции shared_ptr, Важные. Вы перечислили некоторые из них, но вы пропустили другие:

  • Способность иметь shared_ptr в базовый класс. То есть:

    shared_ptr<base> p = make_shared<derived>(...);
    

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

  • static_pointer_cast а также dynamic_pointer_cast (а также reinterpret_pointer_cast в C++17). Все они опираются на различие между управляемым указателем и get указатель.

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

Вы также, кажется, тривиально отвергаете способность управлять указателями, созданными не вами. Это критическая способность, потому что она позволяет вам быть совместимым с другими кодовыми базами. Внутренне вы можете использовать shared_ptr управлять вещами, сделанными библиотекой, которая была написана в 1998 году.

По-своему, вы делите код на две эпохи: до C++11 и после C++11. Ваш shared_ptr ничего не сделает для любого кода, явно не написанного для C++11.

И вещь об объединении всех этих функций в один тип заключается в следующем:

Тебе не нужен еще один.

shared_ptr потому что он обслуживает так много потребностей, может быть эффективно использован практически в любом месте. Возможно, это не самый эффективный тип, но он сделает работу практически в каждом случае. И это не так уж и медленно.

Это обрабатывает совместную собственность с полиморфизмом. Он управляет общим владением объектами-членами. Он управляет общим владением памятью, которую вы не выделяли. Он управляет общим владением памятью с особыми потребностями выделения / освобождения. И так далее.

Если вам нужна семантика совместного владения, и она вам нужна, shared_ptr получает твою спину каждый раз. С вашей предложенной идеей всегда будут ограничения, что-то на вашем пути от выполнения вашей работы.

Тип, который работает, должен быть предпочтен по умолчанию, а не тот, который не работает.

Оглядываясь назад, учитывая make_shared, было бы shared_ptr есть конструктор, который принимает необработанный указатель, если он был введен в C++11?

Что если вы не контролируете распределение объекта? Что делать, если вам нужно использовать пользовательский удалитель? Что делать, если вам нужна инициализация списка вместо паренов?

Ни один из этих случаев не рассматривается make_shared,

Кроме того, если вы используете weak_ptr, shared_ptr распределяется через make_shared не освободит память, пока все weak_ptrs также уничтожены. Таким образом, даже если у вас есть обычный разделяемый указатель, к которому не применимо ни одно из вышеперечисленных, вполне возможно, что вы все равно предпочтете конструктор необработанных указателей.

Еще одна ситуация будет, если ваш тип обеспечивает перегрузки для operator new а также operator delete, Это может сделать его неподходящим для make_shared, поскольку эти перегрузки не будут вызваны - и, по-видимому, они существуют по причине.

std::shared_ptr делает гораздо больше, чем выделяет объекты в куче.

Рассмотрим его использование в качестве автоматического закрытия дескриптора общего файла:

#include <cstdio>
#include <memory>


int main()
{
  auto closer = [](FILE* fp) { std::fclose(fp); };
  auto fp = std::shared_ptr<FILE>(std::fopen("foo.txt", "r"),
                                  closer);
}
Другие вопросы по тегам