15

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

前提 1:C++11 标准

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

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*,否则 [...] 构造函数不得参与重载决议。”

前提 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,请在聊天中这样做。这是一个很长的故事,没有办法在不被误解的情况下总结它(及其所有令人不快的后果)。先感谢您。

4

2 回答 2

19

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

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

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

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

所以你不能改变is_convertible<Y*, T*>and if Y*is convertible to T*then 赋值将起作用,因为(指针和 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然后被我肢解。]

于 2013-01-08T18:43:19.730 回答
6

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

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

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

这导致了您不想回答的原始问题:什么是让您思考此解决方案的用例。或者换一种说法,为什么人们不断地询问解决方案而不是他们想要解决的真正问题?

于 2013-01-08T18:43:08.393 回答