#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>());
Два вопроса.
- Правильно ли я понимаю, что shared_ptr<> требует указания политики уничтожения каждый раз, когда она используется, или я что-то упускаю?
- Если я ничего не упускаю, есть идеи, почему эти два отличаются?
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 *)