【问题标题】:What's wrong with copy-construction of a shared_ptr from an implicit cast?从隐式转换中复制构造 shared_ptr 有什么问题?
【发布时间】:2012-09-08 19:09:33
【问题描述】:

考虑这个最小的例子:

#include <memory>

struct B {
  typedef std::shared_ptr<B> Ptr;
};

struct A {
  operator B::Ptr() { // type conversion operator                  <----+
    return std::make_shared<B>();  //                                   |
  }                                //                                   |
};                                 //                                   |
                                   //                                   |
int main() {                       //                                   |
  A* a = new A;                    //                                   |
  B::Ptr{*a}; // copy construction from a's implicit cast to B::Ptr ----+ 
}

shared_ptr&lt;B&gt; 的这种无害复制构造 在 g++ 4.6.3 x86_64-linux-gnu 上严重失败,但似乎适用于 g++ 4.5(请注意,较新的版本中断,而较旧的作品!)。从错误中我可以看出(见下文)g++ 4.6 似乎通过值传递A,而不是通过(r 或 l)引用。

所以,问题是,哪个是正确的,哪个是错误的?这种行为应该失败吗?如果有,为什么?
据我了解转换规则,此时应该尝试隐式转换为B::Ptr ,对吧?


注意:我将此示例简化为纯粹的技术问题,并且此代码对于任何生产系统都没有意义。

这是准确的错误:

shp.cpp: In function ‘int main()’:
shp.cpp:17:12: error: no matching function for call to ‘std::shared_ptr<B>::shared_ptr(<brace-enclosed initializer list>)’
shp.cpp:17:12: note: candidates are:
/usr/include/c++/4.6/bits/shared_ptr.h:315:2: note: template<class _Alloc, class ... _Args> std::shared_ptr::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...)
/usr/include/c++/4.6/bits/shared_ptr.h:266:17: note: constexpr std::shared_ptr<_Tp>::shared_ptr(std::nullptr_t) [with _Tp = B, std::nullptr_t = std::nullptr_t]
/usr/include/c++/4.6/bits/shared_ptr.h:266:17: note:   no known conversion for argument 1 from ‘A’ to ‘std::nullptr_t’
/usr/include/c++/4.6/bits/shared_ptr.h:258:2: note: template<class _Tp1, class _Del> std::shared_ptr::shared_ptr(std::unique_ptr<_Up, _Ep>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:253:2: note: template<class _Tp1> std::shared_ptr::shared_ptr(std::auto_ptr<_Tp1>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:248:11: note: template<class _Tp1> std::shared_ptr::shared_ptr(const std::weak_ptr<_Tp1>&)
/usr/include/c++/4.6/bits/shared_ptr.h:236:2: note: template<class _Tp1, class> std::shared_ptr::shared_ptr(std::shared_ptr<_Tp1>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:226:7: note: std::shared_ptr<_Tp>::shared_ptr(std::shared_ptr<_Tp>&&) [with _Tp = B, std::shared_ptr<_Tp> = std::shared_ptr<B>]
/usr/include/c++/4.6/bits/shared_ptr.h:226:7: note:   no known conversion for argument 1 from ‘A’ to ‘std::shared_ptr<B>&&’
/usr/include/c++/4.6/bits/shared_ptr.h:218:2: note: template<class _Tp1, class> std::shared_ptr::shared_ptr(const std::shared_ptr<_Tp1>&)
/usr/include/c++/4.6/bits/shared_ptr.h:206:2: note: template<class _Tp1> std::shared_ptr::shared_ptr(const std::shared_ptr<_Tp1>&, _Tp*)
/usr/include/c++/4.6/bits/shared_ptr.h:184:2: note: template<class _Deleter, class _Alloc> std::shared_ptr::shared_ptr(std::nullptr_t, _Deleter, _Alloc)
/usr/include/c++/4.6/bits/shared_ptr.h:165:2: note: template<class _Tp1, class _Deleter, class _Alloc> std::shared_ptr::shared_ptr(_Tp1*, _Deleter, _Alloc)
/usr/include/c++/4.6/bits/shared_ptr.h:146:2: note: template<class _Deleter> std::shared_ptr::shared_ptr(std::nullptr_t, _Deleter)
/usr/include/c++/4.6/bits/shared_ptr.h:129:2: note: template<class _Tp1, class _Deleter> std::shared_ptr::shared_ptr(_Tp1*, _Deleter)
/usr/include/c++/4.6/bits/shared_ptr.h:112:11: note: template<class _Tp1> std::shared_ptr::shared_ptr(_Tp1*)
/usr/include/c++/4.6/bits/shared_ptr.h:103:7: note: std::shared_ptr<_Tp>::shared_ptr(const std::shared_ptr<_Tp>&) [with _Tp = B, std::shared_ptr<_Tp> = std::shared_ptr<B>]
/usr/include/c++/4.6/bits/shared_ptr.h:103:7: note:   no known conversion for argument 1 from ‘A’ to ‘const std::shared_ptr<B>&’
/usr/include/c++/4.6/bits/shared_ptr.h:100:17: note: constexpr std::shared_ptr<_Tp>::shared_ptr() [with _Tp = B]
/usr/include/c++/4.6/bits/shared_ptr.h:100:17: note:   candidate expects 0 arguments, 1 provided

【问题讨论】:

  • 是什么让你认为这与shared_ptr 有任何关系?如果您只是创建某种类型并使用它来代替shared_ptr,会发生什么?
  • 它在 GCC 4.7.1 中工作(除了以反斜杠结尾的 cmets,导致多行注释问题)liveworkspace.org/code/d7713b217a30dc973795698adfd3c1c8,我相信它是正确的。 (但我必须说,我发现使用 operator B::Ptr 创建 B 的新 shared-ptr 实例有点误导,因为该实例似乎不是 A 实例的任何类型的 转换形式。我相信这是因为我们正在处理一个简化的示例。)
  • @NicolBolas:当然,我在发布之前就尝试过,但似乎效果很好。我怀疑它与 g++ 的shared_ptr 实现提供的构造函数重载有关。我不知道为什么旧版本有效。
  • @jogojapan:确实,正如我在 OP 中指出的那样,我试图删除所有对问题不重要的内容。
  • 如果你提供一个自定义的简化智能指针类而不是 shared_ptr,这个问题会更清楚。在 MinGW 上使用 4.7.0 对我来说,您的代码似乎可以正常工作。没有看到 shared_ptr 的具体实现,很难明确地说“这是对的”或“这是错的”。关于这个问题,听起来 gcc 找不到 shared_ptr& 的构造函数,我认为这是您的运算符的类型。有一个构造函数 const shared_ptr & tho...也许可以尝试定义 operator B::Ptr() const{...}?

标签: c++ c++11 compiler-errors g++ type-conversion


【解决方案1】:

在当前版本的标准下代码不正确(我正在查看后标准草案 n3376)。

列表初始化规则指定:

13.3.1.7 通过列表初始化[over.match.list]进行初始化

1 - 当非聚合类类型 T 的对象被列表初始化时 [...]:

  • 如果没有找到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始化列表的元素组成。

然而,当重载决议被应用到B::Ptr的复制构造函数采用单个参数const std::shared_ptr&lt;B&gt; &amp;时,参数列表是(*a),由一个左值类型的元素组成@ 987654330@;重载解析不允许考虑转换函数A::operator B::Ptr

13.3.3.1 隐式转换序列 [over.best.ics]

4 - 但是,当考虑构造函数或用户定义的转换函数的参数时,它是 13.3.1.7 [...] 的候选 [...] 当初始化器列表只有一个元素并且转换为X 的构造函数的第一个参数考虑了某些类 X 或对(可能是 cv 限定的)X 的引用 [...],只考虑标准转换序列和省略号转换序列。

所以g++-4.6拒绝这个代码是正确的; g++-4.7.2 不幸地接受了它,这是不正确的。

正确的编写方法是使用直接初始化 (B::Ptr(*a)) 或 static_cast&lt;B::Ptr&gt;

关于允许转换的限制可以追溯到论文n2672,尽管在该论文中第 13.3.3.1p4 段仅适用于用户定义的转换函数的参数defect 978中增加了对构造函数的额外限制:

978。复制初始化规范不正确

13.3.3.1 [over.best.ics] 第 4 段说,
[...]
这不太对,因为这适用于构造函数参数,而不仅仅是用户定义的转换函数的参数。

当前 13.3.3.1p4 的措辞可以追溯到开创性的 defect 84,它引入了“普通法规则,即只会调用单个用户定义的转换来进行隐式转换 em>”。

我对这个答案有点不安;我已经问过Is it possible to invoke a user-defined conversion function via list-initialization?,看看是否有人可以在这里澄清标准的意图。

【讨论】:

  • 那句话并没有真正解决手头的问题。它谈到了在找不到初始化列表构造函数后回退到非初始化列表构造函数。但手头的问题是是否应该在 also 找不到非初始化列表构造函数之后考虑转换运算符。
  • @fgp 解析构造函数调用时考虑转换运算符;见上文。
  • @fgp 它变得更有趣了;根据 13.3.3.1p4,您的示例是不允许的,尽管这是一个相当奇怪的结果。
猜你喜欢
  • 1970-01-01
  • 2016-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-04
  • 1970-01-01
  • 2013-11-24
相关资源
最近更新 更多