构造函数使用std :: forward

鳄鱼鳄鱼

据我所知,在C ++ 11中有效实现构造函数的两种常见方式是使用其中两种

Foo(const Bar& bar) : bar_{bar} {};
Foo(Bar&& bar)      : bar_{std::move(bar)} {};

或只是一种

Foo(Bar bar) : bar_{std::move(bar)} {};

第一个选项可产生最佳性能(例如,希望在有左值的情况下为单个副本,在有右值的情况下为单步移动),但是需要为N个变量提供2 N个重载,而第二个选项只需要一个函数,而代价是传递左值时的另一步动作。

在大多数情况下,这不应产生太大影响,但可以肯定的是,两种选择都不是最佳选择。但是,也可以执行以下操作:

template<typename T>
Foo(T&& bar) : bar_{std::forward<T>(bar)} {};

这样做的缺点是允许使用可能不需要的类型的变量作为bar参数(我敢肯定,使用模板专业化可以轻松解决此问题),但是无论如何,性能都是最佳的,并且代码随变量的数量线性增长。

为什么没人为此目的使用诸如正向的东西?这不是最理想的方法吗?

Yakk-亚当·内夫罗蒙特

人们做到完美的前向构造函数。

有费用。

首先,代价是它们必须在头文件中。其次,每次使用都会导致创建不同的构造函数。第三,不能{}对构建对象使用类似初始化语法的对象。

第四,它与Foo(Foo const&)Foo(Foo&&)构造函数的交互性很差它不会替换它们(由于语言规则),但是会在上面选择它们Foo(Foo&)这可以通过一些样板SFINAE来解决:

template<class T,
  std::enable_if_t<!std::is_same<std::decay_t<T>, Foo>{},int> =0
>
Foo(T&& bar) : bar_{std::forward<T>(bar)} {};

现在,它不再Foo(Foo const&)是type参数的首选Foo&在此期间,我们可以执行以下操作:

Bar bar_;
template<class T,
  std::enable_if_t<!std::is_same<std::decay_t<T>, Foo>{},int> =0,
  std::enable_if_t<std::is_constructible<Bar, T>{},int> =0
>
Foo(T&& bar) :
  bar_{std::forward<T>(bar)}
{};

现在该构造函数仅在参数可以用于构造的情况下有效bar

接下来要做的就是要么支持的{}样式构造,要么支持bar分段构造,或者支持您前进到bar的varargs构造。

这是一个varargs变体:

Bar bar_;
template<class T0, class...Ts,
  std::enable_if_t<sizeof...(Ts)||!std::is_same<std::decay_t<T0>, Foo>{},int> =0,
  std::enable_if_t<std::is_constructible<Bar, T0, Ts...>{},int> =0
>
Foo(T0&&t0, Ts&&...ts) :
  bar_{std::forward<T0>(t0), std::forward<Ts>(ts)...}
{};
Foo()=default;

另一方面,如果我们添加:

Foo(Bar&& bin):bar_(std::move(bin));

我们现在支持Foo( {construct_bar_here} )语法,这很好。但是,如果我们已经具有上述varardic(或类似的分段构造),则不需要这样做。尽管如此,有时初始化列表还是很不错的转发方式,尤其是当我们bar_在编写代码时不知道类型的时候(例如泛型):

template<class T0, class...Ts,
  std::enable_if_t<std::is_constructible<Bar, std::initializer_list<T0>, Ts...>{},int> =0
>
Foo(std::initializer_list<T0> t0, Ts&&...ts) :
  bar_{t0, std::forward<Ts>(ts)...}
{};

所以如果Bar是一个std::vector<int>我们可以做的Foo( {1,2,3} ),最终{1,2,3}在内部bar_

在这一点上,您会想知道“我为什么不写Foo(Bar)”。搬家真的很贵Bar吗?

在通用的库式代码中,您将需要达到以上所述。但通常您的物体既知名又便宜。因此,写出真正简单,相当正确的代码,Foo(Bar)并完成所有的假装。

在某些情况下,您有N个不便宜的变量,并且想要提高效率,并且不想将实现放在头文件中。

然后,您只需编写一个擦除类型的Bar创建器,该创建器将使用可用于Bar直接创建一个或通过创建一个类型的任何东西,并将创建的结果std::make_from_tuple存储下来。然后,它使用RVO直接Bar在目标位置内构建就地。

template<class T>
struct make {
  using maker_t = T(*)(void*);
  template<class Tuple>
  static maker_t make_tuple_maker() {
    return [](void* vtup)->T{
      return make_from_tuple<T>( std::forward<Tuple>(*static_cast<std::remove_reference_t<Tuple>*>(vtup)) );
    };
  }
  template<class U>
  static maker_t make_element_maker() {
    return [](void* velem)->T{
      return T( std::forward<U>(*static_cast<std::remove_reference_t<U>*>(velem)) );
    };
  }
  void* ptr = nullptr;
  maker_t maker = nullptr;
  template<class U,
    std::enable_if_t< std::is_constructible<T, U>{}, int> =0,
    std::enable_if_t<!std::is_same<std::decay_t<U>, make>{}, int> =0
  >
  make( U&& u ):
    ptr( (void*)std::addressof(u) ),
    maker( make_element_maker<U>() )
  {}
  template<class Tuple,
    std::enable_if_t< !std::is_constructible<T, Tuple>{}, int> =0,
    std::enable_if_t< !std::is_same<std::decay_t<Tuple>, make>{}, int> =0,
    std::enable_if_t<(0<=std::tuple_size<std::remove_reference_t<Tuple>>{}), int> = 0 // SFINAE test that Tuple is a tuple-like
    // TODO: SFINAE test that using Tuple to construct T works
  >
  make( Tuple&& tup ):
    ptr( std::addressof(tup) ),
    maker( make_tuple_maker<Tuple>() )
  {}
  T operator()() const {
    return maker(ptr);
  }
};

代码使用C ++ 17功能,std::make_from_tuple在C ++ 11中相对容易编写。在C ++ 17中,保证省略意味着它甚至适用于不可移动的类型,这确实很酷。

现场例子

现在您可以编写:

Foo( make<Bar> bar_in ):bar_( bar_in() ) {}

的主体Foo::Foo可以移出头文件。

但这比上述替代方案更疯狂。

同样,您是否考虑过只是写作Foo(Bar)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章