Есть ли ошибка в реализации GCC 4.7.2 оператора присваивания shared_ptr (шаблона)?

Мой вопрос касается реализации шаблона оператора присваивания shared_ptr в GCC 4.7.2, который, как я подозреваю, содержит ошибку.

ПРИМЕЧАНИЕ 1: СТАНДАРТ C++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? Я не смог найти ни одного отчета об ошибке для этой проблемы.

ПОСТСКРИПТУМ:

Если вы хотите спросить меня, каковы мои тестовые примеры, почему меня волнует эта, казалось бы, незначительная деталь и почему я, кажется, подразумеваю, что мне нужно специализироваться std::is_convertible, пожалуйста, сделайте это в чате. Это длинная история, и невозможно подвести итог, чтобы не быть понятым неправильно (со всеми вытекающими отсюда неприятными последствиями). Заранее спасибо.


person Andy Prowl    schedule 08.01.2013    source источник
comment
Я думаю, что у вас есть неправильный шаблон конструктора, вы дали тот, который преобразуется из другого необработанного указателя, а не тот, который преобразуется из другого shared_ptr   -  person Jonathan Wakely    schedule 08.01.2013
comment
Даже если вы можете специализировать is_convertible, вы не должны этого делать, если не поддерживаете поведение, требуемое стандартом...   -  person Kerrek SB    schedule 08.01.2013
comment
@JonathanWakely: ты прав, извини. я отредактирую свой вопрос   -  person Andy Prowl    schedule 08.01.2013
comment
[meta.type.syno]/1 Поведение программы, которая добавляет специализации для любого из шаблонов классов, определенных в этом подпункте, не определено, если не указано иное. Есть ли у вас тестовый пример, который не является неопределенным? и ведет себя по-разному для реализаций GCC и MSVC? IIRC Я скопировал этот код оператора присваивания прямо из Boost.   -  person Jonathan Wakely    schedule 08.01.2013
comment
@AndyProwl: Если вы не хотите, чтобы люди спрашивали о ваших тестовых примерах, вы не должны включать какие-либо ссылки на них в вопрос ... Тем не менее, я думаю, что это сработает как ответ вики сообщества, что, на мой взгляд. являются дополнительными дополнениями к другим ответам или вопросу. Но это только я. Причина, по которой это важно, заключается в том, что вы заявляете о дефекте на основании этих тестов; нам нужно проверить, являются ли ваши тесты дефектными.   -  person GManNickG    schedule 08.01.2013
comment
@KerrekSB: понятно. Спасибо, что указали на это.   -  person Andy Prowl    schedule 08.01.2013
comment
@KerrekSB, ты не должен этого делать, точка.   -  person Jonathan Wakely    schedule 08.01.2013
comment
@JonathanWakely: О, хорошо, даже лучше!   -  person Kerrek SB    schedule 08.01.2013
comment
@JonathanWakely: отличная ссылка, спасибо! Но не говоря уже о моей (неправильной) идее специализации std::is_convertible, делает ли это реализацию GCC соответствующей?   -  person Andy Prowl    schedule 08.01.2013
comment
Пишу ответ, подождите :)   -  person Jonathan Wakely    schedule 08.01.2013
comment
Даже если специализация std::is_convertible допустима, я не думаю, что это изменит значение фразы Y*, которая неявно преобразуется в T*.   -  person aschepler    schedule 08.01.2013
comment
@GManNickG: я понимаю, что вы имеете в виду. У вас есть пункт.   -  person Andy Prowl    schedule 08.01.2013
comment
Мне любопытны ваши is_convertible требования. Обратите внимание, что стандарт не требует std::is_convertible<T,U>::value, а требует, чтобы они были конвертируемыми, и это неявно проверяется в назначении. Предоставление специализаций is_convertible может только ограничить использование, поскольку инициализация/назначение по-прежнему должны выполняться в тех случаях, когда тест проходит. Также обратите внимание, что стандарт не требует использования SFINAE или использования std::is_convertible, поэтому в зависимости от этого поведения ваша программа становится непереносимой.   -  person David Rodríguez - dribeas    schedule 08.01.2013
comment
@aschepler: я думаю, Джонатан Уэйкли доказал, что специализироваться на std::is_convertible нехорошо. На самом деле существует, если не указано иное, что заставляет меня задаться вопросом, есть ли случаи, когда это возможно.   -  person Andy Prowl    schedule 08.01.2013
comment
@DavidRodríguez-dribeas: Я понимаю, но на что бы вы ни полагались, стандарт требует, чтобы поведение оператора = было таким же, как у конструктора + подкачки. Вот что я задаю вопрос: это относится к реализации GCC? Это явно для VC10   -  person Andy Prowl    schedule 08.01.2013
comment
@AndyProwl C++ имеет общее правило «как если бы»: если биты не реализованы, как указано в стандарте, но ведут себя так, как если бы они были реализованы — это означает, что правильная программа не может сказать, что они реализованы по-другому — это не ошибка в реализации. . Вот почему вопрос о том, можно ли увидеть другое поведение в программе, имеющей определенное поведение, очень актуален.   -  person    schedule 08.01.2013
comment
@AndyProwl: чат, чтобы другие и я могли видеть ваши тесты. :)   -  person GManNickG    schedule 08.01.2013
comment
@hvd: я понимаю, но стандарт предписывает, что оператор = точно такой же, как конструктор + обмен. Теперь, если конструктор + своп реализован с использованием std::is_convertible, а operator = реализован по-другому, я хотел бы выяснить, эквивалентны ли эти две реализации.   -  person Andy Prowl    schedule 08.01.2013
comment
@AndyProwl: Нет, стандарт предписывает, чтобы поведение operator= было таким же, как и для конструктор+своп в стандарте.   -  person David Rodríguez - dribeas    schedule 08.01.2013
comment
@DavidRodríguez-dribeas: понятно, спасибо за разъяснения   -  person Andy Prowl    schedule 08.01.2013


Ответы (2)


Что меня поражает, так это то, что эта операция не определена в терминах шаблона конструктора или swap().

В этом нет необходимости, ему нужно только вести себя как если бы он был определен в этих терминах.

В частности, простое присвоение _M_ptr = __r._M_ptr не дает того же результата, что и явная проверка типов _Tp1* и _Tp* на конвертируемость через std::is_convertible (которые могут быть специализированными).

Я не согласен: [meta.type.synop]/1 Поведение программы, которая добавляет специализации для любого из шаблонов классов, определенных в этом подпункте, не определено, если не указано иное.

Таким образом, вы не можете изменить значение is_convertible<Y*, T*>, и если Y* преобразуется в T*, то присваивание будет работать, и поскольку оба присваивания (указателя и объекта refcount) равны noexcept, конечный результат эквивалентен обмену. Если указатели не конвертируются, то присваивание не скомпилируется, но shared_ptr(r).swap(*this) тоже не получится, так что оно по-прежнему эквивалентно.

Если я ошибаюсь, отправьте отчет об ошибке, и я исправлю его, но я не думаю, что соответствующая программа сможет обнаружить разницу между реализацией libstdc++ и требованиями стандарта. Тем не менее, у меня не было бы никаких возражений против изменения его для реализации с точки зрения swap. Текущая реализация пришла прямо из shared_ptr в Boost 1.32, Я не знаю, делает ли Boost все так же или сейчас использует shared_ptr(r).swap(*this).

[Полное раскрытие, я сопровождаю libstdc++ и в основном отвечаю за код shared_ptr, который изначально был любезно предоставлен авторами boost::shared_ptr, а затем с тех пор мной изуродован.]

person Jonathan Wakely    schedule 08.01.2013
comment
Текущая версия Boost (1.52.0) реализует его как this_type(r).swap(*this);. Я знаю, что эта реализация существует по крайней мере с версии 1.44.0. Не уверен, когда это было изменено. - person Andy Prowl; 09.01.2013

Реализация в GCC соответствует требованиям стандарта. Когда стандарт определяет, что поведение одной функции эквивалентно другому набору функций, это означает, что эффект первой эквивалентен эффекту вторых функций, как определено в стандарте (а не реализовано).

Стандарт не требует использования std::is_convertible для этого конструктора. Он требует SFINAE для конструктора, но не требует SFINAE для оператора присваивания. Требование конвертируемости типов предъявляется к программе, а не к реализации std::shared_ptr, и это ваша ответственность. Если передаваемые типы не конвертируются, то это ошибка в вашей программе. Если да, то реализация должна принять код, даже если вы хотите запретить его использование, настроив шаблон is_convertible.

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

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

person David Rodríguez - dribeas    schedule 08.01.2013
comment
Он требует SFINAE для этого конструктора: [util.smartptr.shared.const]/17 Второй конструктор не должен участвовать в разрешении перегрузки, если только Y* неявно преобразуется в T*. Но для оператора присваивания SFINAE не требуется, так что это не имеет значения. - person Jonathan Wakely; 08.01.2013
comment
@JonathanWakely: Вы правы, я посмотрел не на тот конструктор. - person David Rodríguez - dribeas; 08.01.2013