Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/163.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 避免构造函数中常量引用和右值引用的指数增长_C++_C++11_Rvalue Reference_Const Reference_C++17 - Fatal编程技术网

C++ 避免构造函数中常量引用和右值引用的指数增长

C++ 避免构造函数中常量引用和右值引用的指数增长,c++,c++11,rvalue-reference,const-reference,c++17,C++,C++11,Rvalue Reference,Const Reference,C++17,我正在为一个机器学习库编写一些模板类,并且我经常遇到这个问题。我主要使用策略模式,其中类接收不同功能的模板参数策略,例如: template <class Loss, class Optimizer> class LinearClassifier { ... } 有没有办法避免这种情况 事实上,这正是引入的原因。将构造函数重写为 template <typename L, typename O> LinearClassifier(L && loss, O

我正在为一个机器学习库编写一些模板类,并且我经常遇到这个问题。我主要使用策略模式,其中类接收不同功能的模板参数策略,例如:

template <class Loss, class Optimizer> class LinearClassifier { ... }

有没有办法避免这种情况

事实上,这正是引入的原因。将构造函数重写为

template <typename L, typename O>
LinearClassifier(L && loss, O && optimizer)
    : _loss(std::forward<L>(loss))
    , _optimizer(std::forward<O>(optimizer))
{}
模板
线性分类器(L&&loss、O&&optimizer)
:_损失(标准::远期(损失))
,_优化器(std::forward(优化器))
{}
但是,按照伊利亚·波波夫在他的报告中的建议去做可能会简单得多。老实说,我通常是这样做的,因为搬家是为了便宜,再多搬一次也不会让事情发生戏剧性的变化


正如Howard Hinnant,我的方法可能不友好,因为现在LinearClassifier接受构造函数中的任何类型对。演示如何处理它。

这正是“传递值并移动”技术的使用案例。 虽然与左值/右值重载相比效率稍低,但也不算太差(多做一步),可以省去麻烦

LinearClassifier(Loss loss, Optimizer optimizer) 
    : _loss(std::move(loss)), _optimizer(std::move(optimizer)) {}
在左值参数的情况下,将有一个拷贝和一个移动,在右值参数的情况下,将有两个移动(前提是类
Loss
Optimizer
实现移动构造函数)


更新:一般来说,更高效。另一方面,此解决方案避免了不总是需要的模板化构造函数,因为当不受SFINAE约束时,它将接受任何类型的参数,如果参数不兼容,则会导致构造函数内部出现硬错误。换句话说,无约束的模板构造函数对SFINAE不友好。有关避免此问题的约束模板构造函数,请参见

模板化构造函数的另一个潜在问题是需要将其放置在头文件中


更新2:Herb Sutter在其2014年CppCon演讲“回归基本”中谈到了这个问题。他首先讨论了按值传递,然后在右值ref上重载,然后是包括约束在内的完美转发。最后,他谈到构造函数是传递值的唯一好的用例。

为了完整性起见,最佳2参数构造函数将使用两个转发引用,并使用SFINAE确保它们是正确的类型。我们可以引入以下别名:

template <class T, class U>
using decays_to = std::is_convertible<std::decay_t<T>*, U*>;

你想在兔子洞下面走多远

我知道解决这个问题有4种合适的方法。如果您符合它们的先决条件,通常应该使用前面的条件,因为后面的每一个条件都会显著增加复杂性


在大多数情况下,要么移动非常便宜,要么两次都是免费的,要么移动就是复制

如果move是copy,而copy是非free,则通过
const&
获取参数。如果不是,则按值计算

这将基本上达到最佳状态,并使您的代码更容易理解

LinearClassifier(Loss loss, Optimizer const& optimizer)
  : _loss(std::move(loss))
  , _optimizer(optimizer)
{}
对于便宜的移动
丢失
和移动是复制
优化器

在所有情况下,这比下面的“最佳”完美转发(注意:完美转发不是最佳)每值参数多移动1步。只要move便宜,这就是最好的解决方案,因为它会生成干净的错误消息,允许基于
{}
的构造,并且比其他任何解决方案都容易阅读

考虑使用此解决方案


如果移动比复制便宜,但不是免费的,一种方法是基于完美的转发: 要么:

我们将构造推迟到
线性分类器内部
。这允许您在对象中拥有非复制/可移动的对象,并且可以说效率最高

要了解这是如何工作的,现在的示例
分段构造
std::pair
一起工作。首先传递分段构造,然后将参数作为元组向前传递(包括复制或移动向量)

通过直接构造对象,与上面的完美转发解决方案相比,我们可以消除每个对象的移动或复制。如果需要,还可以转发副本或移动


最后一个可爱的技巧是键入擦除结构。实际上,这需要像
std::experimental::optional
这样的东西可用,并且可能会使类变得更大一些

这并不比分段构造的速度快。它确实抽象了emplace构造所做的工作,使其在每次使用的基础上更加简单,并且允许您从头文件中拆分主体。但在运行时和空间上都有少量开销

你需要从一大堆样板开始。这将生成一个模板类,该类表示“稍后在其他人告诉我的位置构造对象”的概念

struct延迟放置{};
模板
结构延迟构造{
std::functionctor;
delayed_construct(delayed_construct&)=delete;//类是一次性使用的
延迟构造(延迟构造&&)=默认值;
延迟的_构造():
ctor([](自动操作){op.emplace();})
{}
模板*=nullptr
>
延迟构造(T&&T,Ts&&T…Ts):
延迟构造(延迟放置{},std::forward(t),std::forward(ts)…)
{}
模板
延迟施工(延迟安置、t&t、Ts&t…Ts):
ctor([tup=std::forward_as_tuple(std::forward(t),std::forward(ts)…])(自动和操作)可变{
ctor_helper(op,std::make_index_sequence{},std::move(tup));
})
模板
静态void-ctor\u-helper(std::experimental::optional&op,std::index\u-sequence,std::tuple&&tup){
op.emplace(std::get(std::move(tup))…);
}
void操作符()(标准::实验::可选和目标){
行政主任(目标);;
ctor={};
}
显式运算符bool()常量{return!!ctor;}
};
我们在哪里
template <class L, class O,
          class = std::enable_if_t<decays_to<L, Loss>::value &&
                                   decays_to<O, Optimizer>::value>>
LinearClassifier(L&& loss, O&& optimizer)
: _loss(std::forward<L>(loss))
, _optimizer(std::forward<O>(optimizer))
{ }
LinearClassifier(Loss loss, Optimizer optimizer)
: _loss(std::move(loss))
, _optimizer(std::move(optimizer))
{ }
LinearClassifier(Loss loss, Optimizer const& optimizer)
  : _loss(std::move(loss))
  , _optimizer(optimizer)
{}
template<class L, class O    >
LinearClassifier(L&& loss, O&& optimizer)
  : _loss(std::forward<L>(loss))
  , _optimizer(std::forward<O>(optimizer))
{}
template<class L, class O,
  std::enable_if_t<
    std::is_same<std::decay_t<L>, Loss>{}
    && std::is_same<std::decay_t<O>, Optimizer>{}
  , int> * = nullptr
>
LinearClassifier(L&& loss, O&& optimizer)
  : _loss(std::forward<L>(loss))
  , _optimizer(std::forward<O>(optimizer))
{}
private:
template<std::size_t...LIs, std::size_t...OIs, class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
  std::index_sequence<LIs...>, std::tuple<Ls...>&& ls,
  std::index_sequence<OIs...>, std::tuple<Os...>&& os
)
  : _loss(std::get<LIs>(std::move(ls))...)
  , _optimizer(std::get<OIs>(std::move(os))...)
{}
public:
template<class...Ls, class...Os>
LinearClassifier(std::piecewise_construct_t,
  std::tuple<Ls...> ls,
  std::tuple<Os...> os
):
  LinearClassifier(std::piecewise_construct_t{},
    std::index_sequence_for<Ls...>{}, std::move(ls),
    std::index_sequence_for<Os...>{}, std::move(os)
  )
{}
struct delayed_emplace_t {};
template<class T>
struct delayed_construct {
  std::function< void(std::experimental::optional<T>&) > ctor;
  delayed_construct(delayed_construct const&)=delete; // class is single-use
  delayed_construct(delayed_construct &&)=default;
  delayed_construct():
    ctor([](auto&op){op.emplace();})
  {}
  template<class T, class...Ts,
    std::enable_if_t<
      sizeof...(Ts)!=0
      || !std::is_same<std::decay_t<T>, delayed_construct>{}
    ,int>* = nullptr
  >
  delayed_construct(T&&t, Ts&&...ts):
    delayed_construct( delayed_emplace_t{}, std::forward<T>(t), std::forward<Ts>(ts)... )
  {}
  template<class T, class...Ts>
  delayed_construct(delayed_emplace_t, T&&t, Ts&&...ts):
    ctor([tup = std::forward_as_tuple(std::forward<T>(t), std::forward<Ts>(ts)...)]( auto& op ) mutable {
      ctor_helper(op, std::make_index_sequence<sizeof...(Ts)+1>{}, std::move(tup));
    })
  template<std::size_t...Is, class...Ts>
  static void ctor_helper(std::experimental::optional<T>& op, std::index_sequence<Is...>, std::tuple<Ts...>&& tup) {
    op.emplace( std::get<Is>(std::move(tup))... );
  }
  void operator()(std::experimental::optional<T>& target) {
    ctor(target);
    ctor = {};
  }
  explicit operator bool() const { return !!ctor; }
};
LinearClassifier( delayed_construct<Loss> loss, delayed_construct<Optimizer> optimizer ) {
  loss(_loss);
  optimizer(_optimizer);
}