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

时间:2013-01-08 18:20:44

标签: c++ gcc stl c++11 smart-pointers

我的问题涉及GCC 4.7.2中shared_ptr的赋值运算符模板的实现,我怀疑它包含一个错误。

PREMISE 1:C ++ 11 STANDARD

以下是我正在讨论的赋值运算符模板的签名:

template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;

从C ++ 11标准(20.7.2.2.3):

“相当于 shared_ptr(r).swap(*this) 。”

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

template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;

从C ++ 11标准(20.7.2.2.1):

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

PREMISE 2:GCC 4.7.2的实施:

现在GCC 4.7.2的构造函数模板的实现对我来说似乎是正确的(std::__shared_ptrstd::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时相同的结果专门)。

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);
}

问题

GCC 4.7.2的shared_ptr实施确实存在错误吗?我找不到关于这个问题的任何错误报告。

POST SCRIPTUM

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

2 个答案:

答案 0 :(得分:19)

  

让我感到震惊的是,此操作根据构造函数模板定义,也不是swap()

它不需要,它只需要表现,好像是用这些术语定义的。

  

特别是,简单作业_M_ptr = __r._M_ptr不会产生与通过_Tp1*)明确检查可转换性类型_Tp*std::is_convertible时相同的结果这可以是专业的)。

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

因此,您无法更改is_convertible<Y*, T*>的含义,如果Y*可转换为T*,那么分配将起作用,并且因为两个赋值(指针和引用计数对象)是noexcept最终结果等同于交换。如果指针不可转换,那么赋值将无法编译,但shared_ptr(r).swap(*this)也是如此,所以它仍然是等价的。

如果我错了,那么请提交一份错误报告,我会修复它,但我不认为一致性程序可以检测到libstdc ++实现与标准要求之间的区别。也就是说,我不会反对将其改为以swap来实施。目前的实施直接来自Boost 1.32中的shared_ptr,我不知道Boost是否仍以相同方式执行此操作,或者现在是否使用shared_ptr(r).swap(*this)

[完全披露,我是libstdc ++维护者,并且主要负责shared_ptr代码,该代码最初由boost::shared_ptr的作者亲自捐赠,然后由我自己进行了多次处理。]

答案 1 :(得分:6)

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

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

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

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