Есть ли ошибка в GCC 4.7.2 реализации shared_ptr (templated) оператора присваивания?
Мой вопрос касается реализации шаблона оператора назначения shared_ptr
в GCC 4.7.2, который, как я подозреваю, содержит ошибку.
ПОМЕЩЕНИЕ 1: С++ 11 СТАНДАРТ
Вот подпись шаблона оператора присваивания, о котором я говорю:
template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;
Из стандарта С++ 11 (20.7.2.2.3):
"Эквивалентно shared_ptr(r).swap(*this)
."
Другими словами, шаблон оператора присваивания определяется в терминах шаблона конструктора. Подпись шаблона конструктора выглядит следующим образом:
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
Из стандарта С++ 11 (20.7.2.2.1):
"Требуется: конструктор [...] не должен участвовать в разрешении перегрузки, если Y * неявно конвертируется в T *."
ПОМЕЩЕНИЕ 2: GCC 4.7.2 ОСУЩЕСТВЛЕНИЕ:
Теперь реализация шаблона конструктора GCC 4.7.2 кажется мне верной (std::__shared_ptr
- базовый класс std::shared_ptr
):
template<typename _Tp1, typename =
typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>
__shared_ptr(__shared_ptr<_Tp1, _Lp>&& __r) noexcept
:
_M_ptr(__r._M_ptr),
_M_refcount()
{
_M_refcount._M_swap(__r._M_refcount);
__r._M_ptr = 0;
}
Однако реализация GCC 4.7.2 шаблона оператора присваивания такова:
template<typename _Tp1>
__shared_ptr& operator=(const __shared_ptr<_Tp1, _Lp>& __r) noexcept
{
_M_ptr = __r._M_ptr;
_M_refcount = __r._M_refcount; // __shared_count::op= doesn't throw
return *this;
}
Что меня поражает, так это то, что эта операция не определена в терминах шаблона конструктора или swap()
. В частности, простое присваивание _M_ptr = __r._M_ptr
не дает такого же результата, как если типы _Tp1*
и _Tp*
явно проверяются на конвертируемость через std::is_convertible
(который может быть специализированным).
ПОМЕЩЕНИЕ 3: РЕАЛИЗАЦИЯ VC10
Я заметил, что VC10 имеет более подходящую реализацию в этом отношении, которую я считаю правильным и ведет себя так, как я ожидал в своих тестовых случаях (в то время как GCC не делает):
template<class _Ty2>
_Myt& operator=(const shared_ptr<_Ty2>& _Right)
{
// assign shared ownership of resource owned by _Right
shared_ptr(_Right).swap(*this);
return (*this);
}
ВОПРОС:
Есть ли ошибка в реализации GCC 4.7.2 shared_ptr
? Я не мог найти отчет об ошибке для этой проблемы.
POST SCRIPTUM:
Если вы хотите спросить меня, что такое мои тестовые примеры, почему я забочусь об этой, казалось бы, несущественной детали, и почему я, кажется, подразумеваю, что мне нужно специализироваться на std::is_convertible
, пожалуйста, сделайте это в чате. Это долгая история, и нет способа суммировать ее, не будучи неправильно понятой (со всеми ее неприятными последствиями). Заранее благодарю вас.
Ответы
Ответ 1
Что меня поражает, так это то, что эта операция не определена в терминах шаблона конструктора или swap()
.
Это не обязательно, он должен вести себя так, как если бы он был определен в этих терминах.
В частности, простое присваивание _M_ptr = __r._M_ptr
не дает того же результата, что и в случае, когда типы _Tp1*
и _Tp*
явно проверяются на конвертируемость через std::is_convertible
(который может быть специализированным).
Я не согласен: [meta.type.synop]/1 Поведение программы, которая добавляет специализации для любого из шаблонов классов, определенных в этом подпункте, это undefined, если не указано иное.
Таким образом, вы не можете изменить значение is_convertible<Y*, T*>
, и если Y*
можно конвертировать в T*
, тогда назначение будет работать, и поскольку оба назначения (указателя и объекта refcount) равны noexcept
, конец результат эквивалентен свопу. Если указатели не конвертируемы, то присваивание не будет скомпилировано, но это будет shared_ptr(r).swap(*this)
, поэтому оно все равно эквивалентно.
Если я ошибаюсь, напишите отчет об ошибке, и я исправлю его, но я не думаю, что соответствующая программа может обнаружить разницу между реализацией libstdС++ и требованиями стандарта. Тем не менее, у меня не было бы никаких возражений по поводу его изменения в рамках swap
. Текущая реализация возникла прямо из shared_ptr
в Boost 1.32, я не знаю, будет ли Boost по-прежнему делать то же самое или использовать shared_ptr(r).swap(*this)
сейчас.
[Полное раскрытие, я являюсь сторонником libstdС++ и в основном отвечаю за код shared_ptr
, который изначально был пожертвован авторами boost::shared_ptr
, а затем был изменен мной с тех пор.]
Ответ 2
Реализация в GCC соответствует требованиям стандарта. Когда стандарт определяет, что поведение одной функции эквивалентно другому набору функций, это означает, что эффект первого эквивалентен эффекту последних функций, определенных в стандарте (а не как реализованному).
Стандарт не требует использования std::is_convertible
для этого конструктора. Для конструктора требуется SFINAE, но для оператора присваивания не требуется SFINAE. Требование о том, чтобы типы конвертируемых были помещены в программу, а не на реализацию std::shared_ptr
, и это ваша ответственность. Если типы, переданные внутри, не являются конвертируемыми, то это ошибка в вашей программе. Если они тогда, реализация должна принять код, даже если вы хотите отключить использование, специализируясь на шаблоне is_convertible
.
Опять же, специализация is_convertible
для ограничения преобразований указателей - это поведение undefined, поскольку вы изменяете семантику базового шаблона, который явно запрещен в стандарте.
Это приводит к исходному вопросу, на который вы не хотите отвечать: какой прецедент заставил вас задуматься над этим решением. Или сказал иначе: почему люди продолжают спрашивать о решениях, а не о реальных проблемах, которые они хотят решить?