【问题标题】:SFINAE away a copy constructorSFINAE 离开了一个拷贝构造函数
【发布时间】:2015-06-08 10:04:27
【问题描述】:

在某些条件下,我希望 SFINAE 去掉类模板的复制构造函数和复制赋值运算符。但是如果我这样做,就会生成一个默认的复制构造函数和一个默认的赋值运算符。 SFINAE 是基于我作为类模板参数传递的标签完成的。问题是,SFINAE 仅适用于模板,而复制构造函数/赋值运算符不能是模板。是否有解决方法?

【问题讨论】:

  • 使用根据模板参数禁止复制构造函数/赋值的基类。
  • 由于模板构造函数不是复制构造函数,它也不会阻止复制构造函数被隐式声明,这有时会比你的模板构造函数更受欢迎,所以这似乎是一个死胡同. @stefan 的评论似乎是一个很好的答案。

标签: c++ templates copy-constructor sfinae overload-resolution


【解决方案1】:

此解决方案使用有条件不可复制的基类(通过将复制构造函数和复制赋值运算符显式标记为已删除)。

template <bool>
struct NoCopy;

template <>
struct NoCopy<true>
{
   // C++11 and later: marking as deleted. Pre-C++11, make the copy stuff private.
   NoCopy(const NoCopy&) = delete;
   NoCopy& operator=(const NoCopy&) = delete;
   protected:
      ~NoCopy() = default; // prevent delete from pointer-to-parent
};

template <>
struct NoCopy<false>
{
   // Copies allowed in this case
   protected:
      ~NoCopy() = default; // prevent delete from pointer-to-parent
};

示例用法:

template <typename Number>
struct Foo : NoCopy<std::is_integral<Number>::value>
{
   Foo() : NoCopy<std::is_integral<Number>::value>{}
   {
   }
};

int main()
{
   Foo<double> a;
   auto b = a; // compiles fine
   Foo<int> f;
   auto g = f; // fails!
}

注意:NoCopy 的析构函数被声明为protected 以避免虚拟继承(感谢@Yakk 的提示)。

【讨论】:

  • 注意:@Walter 对copyable 的实现稍微优雅一些​​(因为它只特化了特殊情况)。但显然这两种方法都可以正常工作。
  • NoCopy 在这两种情况下都应该有一个受保护的析构函数,因为它是一个打算从中继承的类,我们不想强制 virtual 开销。就像实施质量问题一样。
  • @Yakk 感谢您的评论。我还不知道那个语言细节:)
  • 另外,我从课程正文中删除了&lt;true&gt;。在模板类本身中,模板类的名称可以在没有模板参数的情况下用于引用当前类类型。这消除了很多混乱。 :)
  • @DmitryJ No, that isn't a problem 如果你真的想要,你可以绕过NoCopy&lt;true&gt;。毕竟我们需要能够构造Foo,并且复制构造函数可以使用相同的父构造函数。 NoCopy&lt;true&gt; 只是禁用复制默认情况下
【解决方案2】:

从可复制或不可复制的基派生的方法是此类问题的标准习语(另请参见 Stefan 的评论)。一种实现方式如下:

template<bool> struct copyable
{
protected:
  ~copyable() = default;
};

template<> struct copyable<false> 
{
  copyable(copyable const&) = delete;
  copyable&operator=(copyable const&) = delete;
protected:
  ~copyable() = default;
};

template<bool allow_copy>
class some_class : copyable<allow_copy> { /* ... */ };

【讨论】:

  • 是的,这就是专业化路线。
  • @user1095108 不,这不是专业化路线。它使用专业化作为实现细节,但它不包括您的some_class 的专业化。最后,copyable 在这两种情况下都应该有protected: ~copyable()=default
  • @Yakk 好的,我添加了受保护的析构函数。那有什么意义呢?是否使 copyable 成为纯基类?
  • @Walter 阻止具有指向copyable 的指针的人在其上调用delete:任何打算派生的类(通常)都应该有一个protected 析构函数或virtual析构函数,因为删除基本类型是那些难以追踪的“哎呀”案例之一。在这种情况下,copyable 的实例(不是基类)显然是无稽之谈,所以也没有什么坏处。
  • @Yakk 顺便说一句,地址清理程序是否会捕获诸如错误之类的删除?
【解决方案3】:

在 C++20 中,我们可以使用 requires 子句来约束特殊的成员函数:

template <typename T>
class C {
public:
    // ...
    C(const C&) requires std::is_copy_constructible_v<T> // for example
    {
        // ...
    }
    C(C&&) requires std::is_move_constructible_v<T> // for example
    {
        // ...
    }
    // ...
};

requires-clause 不会使函数成为函数模板,因此这些函数仍然有资格作为特殊成员函数并阻止生成的默认特殊成员函数。你甚至可以有多个,比如说,复制构造函数,只要它们有不同的约束。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-09
    • 1970-01-01
    相关资源
    最近更新 更多