GCC 4.7.2的shared_ptr(模板化)赋值运算符的实现是否存在错误?

And*_*owl 15 c++ gcc stl smart-pointers c++11

我的问题涉及shared_ptrGCC 4.7.2中的赋值运算符模板的实现,我怀疑它包含一个bug.

PREMISE 1:C++ 11标准

这是我正在谈论的赋值运算符模板的签名:

template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;
Run Code Online (Sandbox Code Playgroud)

从C++ 11标准(20.7.2.2.3):

"相当于 shared_ptr(r).swap(*this)."

换句话说,赋值运算符模板是根据构造函数模板定义的.构造函数模板的签名如下:

template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
Run Code Online (Sandbox Code Playgroud)

从C++ 11标准(20.7.2.2.1):

"要求:[...]构造函数不应参与重载决策,除非Y*可隐式转换为T*."

PREMISE 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;
}
Run Code Online (Sandbox Code Playgroud)

但是,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;
}
Run Code Online (Sandbox Code Playgroud)

令我印象深刻的是,这个操作没有根据构造函数模板定义,也没有定义swap().特别是,普通赋值_M_ptr = __r._M_ptr不会产生与类型相同的结果,_Tp1*并且_Tp*通过std::is_convertible(可以是专门的)显式检查可转换性.

PREMISE 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);
}
Run Code Online (Sandbox Code Playgroud)

问题:

GCC 4.7.2的实现确实存在错误shared_ptr吗?我找不到任何针对此问题的错误报告.

POST SCRIPTUM:

如果你想问我测试用例是什么,为什么我要关心这个看似不重要的细节,为什么我似乎暗示我需要专攻std::is_convertible,请在聊天中这样做.这是一个很长的故事,没有办法总结它而不会被误解(带来所有令人不快的后果).先感谢您.

Jon*_*ely 19

令我印象深刻的是,这个操作没有根据构造函数模板定义,也没有定义swap().

它不需要,它只需要表现得就像在那些术语中定义的那样.

特别是,普通赋值_M_ptr = __r._M_ptr不会产生与类型相同的结果,_Tp1*并且_Tp*通过std::is_convertible(可以是专门的)显式检查可转换性.

我不同意:[meta.type.synop]/1 除非另有说明,否则为本子条款中定义的任何类模板添加特殊化的程序的行为是未定义的.

所以你不能改变它的意思,is_convertible<Y*, T*>如果Y*是可转换的T*那么赋值将起作用,因为指针和指针和引用对象的两个赋值都是noexcept最终结果相当于交换.如果指针不可转换,则赋值将无法编译,但也会shared_ptr(r).swap(*this)如此,因此它仍然是等效的.

如果我错了,那么请提交错误报告,我会修复它,但我不认为一致性程序可以检测到libstdc ++实现与标准要求之间的区别.也就是说,我不会反对改变它来实施swap.当前实现词直接来自shared_ptr升压1.32,我不知道是否加速仍然没有用同样的方式,或者如果它使用shared_ptr(r).swap(*this)了.

[完全披露,我是一个libstdc ++维护者,并且主要负责shared_ptr代码,这些代码最初是由作者boost::shared_ptr亲自捐赠的,然后由我自己进行了批评.]


Dav*_*eas 6

GCC中的实现符合标准中的要求.当标准定义一个函数的行为等同于一组不同的函数时,它意味着前者的效果等同于标准中定义的后一个函数的效果(而不是实现).

该标准不要求使用 std::is_convertible该构造函数.它需要SFINAE作为构造函数,但它不需要SFINAE作为赋值运算符.类型可转换的要求放在程序上,而不是实施,std::shared_ptr这是你的责任.如果传入的类型不可转换,那么它就是程序中的错误.如果它们是,那么实现必须接受代码,即使您想通过专门化is_convertible模板来禁用该代码.

然后,专门is_convertible限制指针转换是未定义的行为,因为您正在更改基本模板的语义,这在标准中是明确禁止的.

这导致了您不想回答的原始问题:使您了解此解决方案的用例是什么.或者说不然,为什么人们继续询问解决方案而不是他们想解决的真正问题?

  • ****确实**需要SFINAE用于该构造函数:[util.smartptr.shared.const]/17 _第二个构造函数不应参与重载决策,除非`Y*`可以隐式转换为`T*`._但它对于赋值运算符不需要SFINAE,因此它无关紧要. (3认同)