【问题标题】:Protecting copy constructor with std::enable_if使用 std::enable_if 保护复制构造函数
【发布时间】:2017-05-12 03:14:19
【问题描述】:

我编写了一个类来促进类型擦除,它具有以下构造函数:

class Envelope {
public:
    Envelope() {}

    template<typename Runnable>
    Envelope(Runnable runnable)
        : m_runFunc(&Envelope::RunAndDeleteRunnable<Runnable>), m_runnable(new Runnable(runnable)) {
    }

    template<typename Runnable>
    Envelope(Runnable * runnable)
        : m_runFunc(&Envelope::RunRunnable<Runnable>), m_runnable(runnable) {
    }
};

我想重写第一个非默认构造函数以获取引用而不是值(Runnable &amp; runnable 而不是 Runnable runnable),但如果我这样做,那么使用非常量复制 Envelope 就像这样

Envelope next(...);
Envelope otherNext(next);

调用该构造函数而不是复制构造函数,我得到堆栈溢出。

我想我可以防止在 Runnable == Envelopestd::enable_if 这样的情况下调用该构造函数

template<typename Runnable = typename std::enable_if<std::negate<std::is_same<Runnable, Nova::Envelope>>::value, Runnable>::type>
Envelope(Runnable & runnable)
    : m_runFunc(&Envelope::RunAndDeleteRunnable<Runnable>), m_runnable(new Runnable(runnable)) {
}

它编译得很好(尽管它在 Visual Studio 2015 中触发了一些智能感知错误,这有点烦人),但它并不能阻止使用非 const Envelopes 调用该构造函数并触发堆栈溢出。

我不完全确定我在这里做错了什么。

【问题讨论】:

  • std::negate 是一元 -,而不是一元 !。这意味着它实际上并没有做任何事情,因为它正在将 1 更改为 -1(这仍然是真实的),并且不会更改为零。你的意思可能是std::enable_if&lt;!std::is_same&lt;Runnable, Nova::Envelope&gt;::value, Runnable&gt;::type&gt;
  • @cdhowie 这可能值得回答,IMO。

标签: c++ c++11 templates template-meta-programming typetraits


【解决方案1】:

防止这种情况的最简单方法是添加一个“非常量”复制构造函数:

class Envelope {
public:
    Envelope() {}
    Envelope(const Envelope&) = default;
    Envelope(Envelope& e) : Envelope(const_cast<const Envelope&>(e)) {}
    ...
    }
};

您并没有做任何特别错误的事情,只是当您编写带有一个模板化参数(或可变参数)的构造函数时,它们往往是“粘滞的”并拦截了打算用于复制构造函数的东西。在选择调用哪个函数时,复制构造函数没有得到我所知道的任何特殊处理。模板化函数更适合非常量对象。通过添加一个与非常量情况匹配的具体(非模板),现在该函数和模板之间的匹配优度将建立联系。在平局的情况下,函数总是胜过模板。

【讨论】:

  • 我已经将这些构造函数逐字复制到我的 Envelope 类和一个没有其他成员的新类中,并且都给了我error C2580: multiple versions of a defaulted special member functions are not allowed
  • @nzerbzer 您粘贴的代码不正确,或者您的编译器有错误:coliru.stacked-crooked.com/a/d820b7dc64e549c2
  • 看起来这是MSVC2015中的一个错误然后:(
【解决方案2】:

我想你可以写类似的东西

template <typename Runnable,
          typename = typename std::enable_if<
             false == std::is_same<Runnable, Nova::Envelope>::value
          >::type>
Envelope (Runnable const & runnable)
    : m_runFunc(&Envelope::RunAndDeleteRunnable<Runnable>),
      m_runnable(new Runnable(runnable))
 { }

-- 编辑--

正如 W.F. 所指出的。 (谢谢),下面的解决方案更好

template <typename Runnable,
          typename std::enable_if<
             false == std::is_same<Runnable, Nova::Envelope>::value
          >::type * = nullptr>
Envelope (Runnable const & runnable)
    : m_runFunc(&Envelope::RunAndDeleteRunnable<Runnable>),
      m_runnable(new Runnable(runnable))
 { }

因为如果您需要两个(或更多)模板构造函数,它们不能仅针对默认模板类型参数而有所不同; W.F.的解决方案避免了这个问题。

【讨论】:

  • @W.F. - 嗯......你的方式也应该有效,但是......请你解释一下(一个例子?)你的意思是“触发不匹配的条件可能有问题”?
  • @W.F. - 你说得对;谢谢。不确定原因;修改了我的答案;你认为我的解释正确吗?
  • 是的,我认为您指出的原因是可以的。 PS。我的昵称更 W.F.不是 W.C. :)
  • @W.F. - 哦!对不起。
  • 啊啊!尤达条件句。我的眼睛。当一个简单的! 就足够时,尤其没有必要。
【解决方案3】:
template<
  class Runnable,
  std::enable_if_t<
    !std::is_same<std::decay_t<Runnable>, Envelope>{},
    int
  > =0
>
Envelope(Runnable&& runnable) : 
  m_runFunc(&Envelope::RunAndDeleteRunnable<std::decay_t<Runnable>>),
  m_runnable(new std::decay_t<Runnable>(std::forward<Runnable>(runnable)))
{}

我确实使用了 C++14 风格的 _t 一些特征的变体;如果您卡在 C++11 中,只需编写自己的帮助程序,typename 垃圾邮件不值得在客户端代码中使用。

这是一个完美的转发构造函数。它通过转发引用消耗任何不是Envelope 的东西。

我们使用std::decay_t 生成Runnable 类型的“可存储”版本,并将Runnable&amp;&amp; 完美转发到其中。

我要做的另一项更改是将new 调用替换为std::make_unique,作为不使用原始新原始指针的一般策略的一部分。

Live example.

【讨论】:

  • 你确定这在语法上是正确的吗?我无法编译它。
  • @nzerbzer 添加了在 g++ 上运行的实时示例。
  • 谢谢。使用unique_ptr 是个好主意,但是这个类必须能够保存它不拥有的对象,所以我不知道它是否适合。
  • @nzerbzer 我明白了。 :) 您可以为此使用unique_ptr&lt;T, void(*)(T*)&gt;。或unique_ptr&lt;T, maybe_delete&lt;T&gt;&gt;。我的论点是避免在同一类型中编写业务逻辑和资源管理。
猜你喜欢
  • 2022-11-20
  • 1970-01-01
  • 1970-01-01
  • 2018-08-23
  • 2012-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-03
相关资源
最近更新 更多