完美转发不一定完美

文摘   2024-10-09 12:06   北京  

你好,我是雨乐~

完美转发是C++中的一种技术,允许函数将参数完美地转发给其他函数,保留其值类别(如左值或右值)。它通常通过std::forward实现,确保在转发时不会丢失任何信息。

今天,我们并非聊其使用或者机制(相信这些已经有很多优秀的相关文章了),而是聊聊完美转发在某些特定场景下的使用限制。

先看下下面两个类:

struct X{
int a;
int b;
};

structY{
Y(X x){}
};

int main() {
Y y({12});

return0;
}

其中,{1,2}被推导为class X

再看下面这段代码:

std::shared_ptr<Y> ptr(new Y({12}));

这种写法也是没问题的,再看下面这种写法:

std::shared_ptr<Y> ptr1 = std::make_shared<Y>({12});

gcc14.2下报错如下:

<source>: In function 'int main()':
<source>:15:48: error: no matching function for call to 'make_shared<Y>(<brace-enclosed initializer list>)'
   15 |   std::shared_ptr<Y> ptr1 = std::make_shared<Y>({12});
      |                             ~~~~~~~~~~~~~~~~~~~^~~~~~~~
In file included from /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/memory:80,
                 from <source>:1:
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/shared_ptr.h:1003:5: note: candidate: 'std::shared_ptr<std::_NonArray<_Tp> > std::make_shared(_Args&& ...) [with _Tp = Y; _Args = {}; _NonArray<_Tp> = Y]'
 1003 |     make_shared(_Args&&... __args)
      |     ^~~~~~~~~~~
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/shared_ptr.h:1003:5: note:   candidate expects 0 arguments, 1 provided
  
// ...

从上面报错信息,需要0个参数,但是提供了1个...

再看看clang的报错,如下:

<source>:15:29: error: no matching function for call to 'make_shared'
15 |   std::shared_ptr<Y> ptr1 = std::make_shared<Y>({12});
      |                             ^~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:1005:5: note: candidate template ignored: substitution failure [with _Tp = Y]: deduced incomplete pack <(no value)> fortemplate parameter '_Args'
1003 |   template<typename _Tp, typename... _Args>
      |                                      ~~~~~
1004 |     inline shared_ptr<_NonArray<_Tp>>
1005 |     make_shared(_Args&&... __args)
      |     ^
/opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:1037:5: note: candidate template ignored: substitution failure [with _Tp = Y]: constraints not satisfied for alias template'_UnboundedArray' [with _Tp = Y]
1036 |     inline shared_ptr<_UnboundedArray<_Tp>>
      |            ~~~~~~~~~~
1037 |     make_shared(size_t __n)
      |     ^
/opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:1095:5: note: candidate template ignored: substitution failure [with _Tp = Y]: constraints not satisfied for alias template'_BoundedArray' [with _Tp = Y]
1094 |     inline shared_ptr<_BoundedArray<_Tp>>
      |            ~~~~~~~~~~
1095 |     make_shared(constremove_extent_t<_Tp>& __u)
      |     ^
/opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:1080:5: note: candidate function templatenot viable: requires0 arguments, but 1 was provided
1080 |     make_shared()
      |     ^
/opt/compiler-explorer/gcc-13.2.0/lib/gcc/x86_64-linux-gnu/13.2.0/../../../../include/c++/13.2.0/bits/shared_ptr.h:1053:5: note: candidate function templatenot viable: requires2 arguments, but 1 was provided
1053 |     make_shared(size_t __n, constremove_extent_t<_Tp>& __u)
      |     ^           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

嗯,错误原因与gcc一样,下面我们看下make_shared的实现:

  template<typename_Tp,typename..._Args>
inline shared_ptr<_NonArray<_Tp>>
make_shared(_Args&&... __args)
{
using_Alloc= allocator<void>;
_Alloc __a;
returnshared_ptr<_Tp>(_Sp_alloc_shared_tag<_Alloc>{__a},
                 std::forward<_Args>(__args)...);
    }

也就是说在使用make_shared的时候,用到了std::forward即完美转发。

结合我们前面的示例以及报错信息,可知:std::forward对某些情况下类型的推导会失败,进而导致我们常说的转发失败,所以为了解决前面的问题,我们需要显式指定类型

  std::shared_ptr<Y> ptr = std::make_shared<Y>(X{12});


以上~~

如果对本文有异议或者有其他技术问题,可以加微信交流:

推荐阅读  点击标题可跳转

1、呃,竟然可以编译通过

2、for循环的演变:从C++03到C++20

3、性能大杀器:与std::endl的较量

雨乐聊编程
毕业于中国科学技术大学,现任某互联网公司高级技术专家一职。本公众号专注于技术交流,分享日常有意思的技术点、线上问题等,欢迎关注