unique_ptr v shared_ptr в отношении политики уничтожения

#c #c 11

#c #c 11

Вопрос:

Я изучал интеллектуальные указатели, которые являются частью C 0x, и столкнулся с чем-то, что мне кажется непоследовательным. В частности, как обрабатывается политика уничтожения unique_ptr<> и shared_ptr<>.

Для unique_ptr<> вы можете специализировать std::default_delete<> и с этого момента, если вы явно не запросите другую политику уничтожения, будет использоваться новое значение по умолчанию.

Рассмотрим следующее:

 struct some_c_type;

some_c_type *construct_some_c_type();
void destruct_some_c_type(some_c_type *);

namespace std  {
    template <> struct default_delete<some_c_type> {
        void operator()(some_c_type *ptr) {
            destruct_some_c_type(ptr);
        }
     };
}
  

Теперь, когда это установлено, unique_ptr<> по умолчанию будет использовать соответствующую политику уничтожения:

 // Because of the specialization, this will use destruct_some_c_type
std::unique_ptr<some_c_type> var(construct_some_c_type());
  

Теперь сравните это с shared_ptr<>. С помощью shared_ptr<> вам необходимо явно запросить соответствующую политику уничтожения, иначе по умолчанию используется operator delete:

 // error, will use operator delete 
std::shared_ptr<some_c_type> var(construct_some_c_type());

// correct, must explicitly request the destruction policy
std::shared_ptr<some_c_type> var(construct_some_c_type(),
                                 std::default_delete<some_c_type>());
  

Два вопроса.

  1. Правильно ли я понимаю, что shared_ptr<> требует указания политики уничтожения каждый раз, когда она используется, или я что-то упускаю?
  2. Если я ничего не упускаю, есть идеи, почему эти два отличаются?

P.S. Причина, по которой меня это волнует, заключается в том, что моя компания много занимается смешанным программированием на C и C . Коду C часто приходится использовать объекты в стиле C, поэтому для меня очень важна простота указания другой политики уничтожения по умолчанию.

Комментарии:

1. Это предполагается std::default_delete<some_c_type>() ? И, кстати, если это не исправлено в C 0x, и я собираюсь это выяснить, все три из них вызывают беспокойство.

2. @GMan — спасибо, это была опечатка. Почему вы считаете их все неприятными (лично я нахожу unique_ptr<> довольно чистым дизайном)?

3. @RSamuel: Я имел в виду неприятный синтаксический анализ; в C 03 это все объявления функций.

4. Кстати, если никто не ответит на это, я бы рекомендовал опубликовать это на comp.lang.c .moderated, я уверен, что кто-то там знает. Я сделаю это, если вы хотите, мне это очень любопытно.

5. @GMan: в данных обстоятельствах это действительно скорее вопрос comp.std.c . Ни то, ни другое на самом деле не является частью C , как определено в настоящее время, и, по крайней мере, для меня, реальный вопрос заключается в следующем: «Является ли это различие между shared_ptr безвозмездным или преднамеренным?» Прямо сейчас, похоже , что это может быть безвозмездно, но…

Ответ №1:

Я думаю, вопрос сводится к тому, почему std::shared_ptr не может иметь связанного deleter (в этом случае он просто вызывает delete ) вместо того, чтобы создавать std::default_delete по умолчанию. (Понятия не имею. Если бы это было предназначено default_delete для специализации, можно было бы ожидать, что оно будет использоваться shared_ptr .)

В противном случае возможны компромиссы.

Лучше иметь меньше аргументов шаблона. В ссылке Boost упоминается, что это позволяет фабрике изменять схему распределения, не затрагивая пользователя фабрики.

unique_ptr С другой стороны, предполагается, что A должен быть очень легким. Как бы вы сохранили средство удаления с нулевыми затратами пространства (в случае функтора без членов), если бы оно не было частью типа (GCC использует tuple, где объекты без членов не занимают места в памяти)?


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

 unique_ptr<FILE, FCloser> f(fopen(x, y));
  

Для

 unique_ptr<FILE> f(fopen(x, y)); //default_delete<FILE> has been specialized
  

В первом случае нечего гадать. Если ресурс получен не из new или new[] , удаление должно быть задано явно.

Комментарии:

1. Я согласен: явное лучше, чем неявное.

2. @UncleBens — одна из проблем с неявным / явным обсуждением заключается в том, что стандарт специально допускает неявные, которые всегда будут компилироваться, но могут вызвать неопределенное поведение. Если вам всегда приходилось указывать средство удаления даже для простого delete или delete [] , это может быть нормально. Но как бы то ни было, об этом легко забыть, сборка будет успешной, и такую ошибку можно легко пропустить.

3. Специализировать его для FILE и т.п. на static_assert? 🙂 Но даже если вы специализируетесь на этом, вам нужно будет не забыть включить заголовок со специализациями?

4. для таких типов, как FILE, вам необходимо включить файл заголовка. Но для локальных типов C мы можем включить специализацию в тот же файл, который объявляет конструктор / деструктор типа C.

5. Проблема с этим синтаксисом заключается в том, что он уродует такие вещи, как взаимодействие с C api. Я хотел бы иметь возможность делать что-то вроде unique_ptr<THandle> ptr(LIB_T_Make_a_T(параметры ….), LIB_T_Destroy_a_T); где тип LIB_T_Destroy_a_T равен void(THandle *)