条件平凡特殊成员函数

C++标准委员会目前关注于将语言添加到可以简化代码的语言中。C++ 20中的一个小例子是 条件平凡特殊成员函数 ,我们在中添加了对的支持 Visual Studio 2019版本16.8 . 它的好处不是很明显,除非你已经深入到了高性能库创作的兔子洞里,所以我写这篇文章是为了向你展示它如何在不需要大量模板魔法的情况下提高某些泛型类型的效率。

null

包裹其他类型的类型在C++世界中是常见的:配对、元组、optoScript、适配器等等。对于其中一些实现,不能使用默认的特殊成员函数(默认构造函数、复制/移动构造函数、复制/移动赋值、析构函数),因为还有一些额外的工作需要做。以这个为例 std::optional -相似类型:

template <typename T>
struct optional {
   bool has_value_;
   union {
      T value_;
      char empty_; //dummy member
   };
};

它有一个 bool 成员来说明它当前是否正在存储值,以及当 optional 是空的。

默认的特殊成员在这里不起作用:当union成员具有非平凡的构造函数和析构函数时,我们需要在 optional 类型。重点介绍复制构造函数,下面是一个潜在的实现:

   optional(optional const& rhs)
      : has_value_(rhs.has_value_), empty_()
   {
      if (has_value_) {
         new (&value_) T(rhs.value_);
      }
   }

我们检查一下 rhs 有一个值,如果有,我们就用它来复制和构造我们自己的值。

但这里有一个性能问题。假设我们复制一份 optional<int> ,如下所示:

optional<int> make_copy(optional<int> const& o) {
  return o;
}

int s是 可构造的平凡复制 (即,可以通过复制内存来复制它们,而不必使用任何构造函数),复制 optional<int> 应该 只需要复制它的字节表示。但这是编译器生成的代码 make_copy :

      movzx eax, BYTE PTR [rdx]   #load o
      mov BYTE PTR [rcx], al      #copy.has_value_ = rhs.has_value_
      test al, al                 #test rhs.has_value_
      je SHORT $EMPTY             #if it’s empty, jump to the end
      mov eax, DWORD PTR [rdx+4]  #load rhs.value_
      mov DWORD PTR [rcx+4], eax  #store to copy.value_
$EMPTY:
      mov rax, rcx                #return copy
      ret 0

我们真正想要的是一种使用默认特殊成员的方法,如果 T 是琐碎的,否则使用我们的自定义一个。

一开始似乎可行的方法是 std::enable_if 根据的属性在默认和自定义复制构造函数实现之间进行选择 T :

template <class U = T, 
          std::enable_if_t<std::is_copy_constructible_v<U> && 
                           std::is_trivially_copy_constructible_v<U>>* = nullptr>
optional(optional const& rhs) = default;

template <class U = T, 
          std::enable_if_t<std::is_copy_constructible_v<U> &&
                           !std::is_trivially_copy_constructible_v<U>>* = nullptr>
optional(optional const& rhs)
      : has_value_(rhs.has_value_), empty_()
{
   if (has_value_) {
   new (&value_) T(rhs.value_);
  }
}

不幸的是,除了默认构造函数之外的特殊成员不能是模板,所以这不起作用。

常见的解决方案 工作是将模板的存储和特殊成员拆分为基类,并通过检查相关的类型特征来选择要从中继承的基类。它的实现相当复杂,所以我已经在本文的底部为那些想要看到它的人解释了它。

如果我们做了这个改变,那么 make_copy 变成这样:

      mov rax, QWORD PTR [rdx]   #load o
      mov QWORD PTR [rcx], rax   #copy memory
      mov rax, rcx               #return copy
      ret 0

现在我们有了更高效的代码生成,但是一个棘手的C++负载难以编写、维护,编译器也在高效地构建。C++ 20让我们保持高效的组装,大大简化了C++。

虽然我们的 std::enable_if 上面的解决方案是行不通的,因为这些函数不能是模板 可以 用C++ 20概念约束非模板函数:

optional(optional const&) = default;

optional(optional const& rhs)
requires std::copy_constructible<T> && !std::is_trivially_copy_constructible_v<T>
    : has_value_(rhs.has_value_), empty_()
{
   if (has_value_) {
   new (&value_) T(rhs.value_);
  }
}

现在 optional<T> 当且仅当 T 用最小的模板魔法。我们既有高效的代码生成,又有C++,它们比以前更容易理解和维护。

正如承诺的,这里是如何在C++ 17中实现这一点。

我们首先将存储拆分为自己的基类:

template <class T>
struct optional_storage_base {
   optional_storage_base() :
     has_value_(false), empty_()
   {}
   bool has_value_;
   union {
      T value_;
      char empty_;
   };
};

然后,我们有一个复制构造函数的基类 T 是可以复制构造的,我们引入了一个默认的模板参数,稍后将专门化它。

template <class T, bool = std::is_trivially_copy_constructible_v<T>>
struct optional_copy_base : optional_storage_base<T> {
  //default copy ctor
   optional_copy_base(optional_copy_base const&) = default;

  //have to default other special members
   ~optional_copy_base() = default;
   optional_copy_base() = default;
   optional_copy_base(optional_copy_base&&) = default;
   optional_copy_base& operator=(optional_copy_base const&) = default;
   optional_copy_base& operator=(optional_copy_base &&) = default;
};

然后我们专门化这个模板 T 简单复制可构造:

template <class T>
struct optional_copy_base<T, false> : optional_storage_base<T> {
   optional_copy_base(optional_copy_base const& rhs)
   {
      if (rhs.has_value_) {
         this->has_value_ = true;
         new (&this->value_) T(rhs.value_);
      }
   }

   //have to default other special members
   ~optional_copy_base() = default;
   optional_copy_base() = default;
   optional_copy_base(optional_copy_base&&) = default;
   optional_copy_base& operator=(optional_copy_base const&) = default;
   optional_copy_base& operator=(optional_copy_base &&) = default;
};

然后我们做可选的inherit from optional_copy_base<T> :

template <typename T>
struct optional : optional_copy_base<T> {
   //other members
};

然后我们对move构造函数、析构函数、复制赋值和move赋值运算符重新执行此操作。这到底是什么 标准库实现者必须通过 以实现和维护负担为代价获得最佳的codegen。这不好玩,相信我。

下载 Visual Studio 2019版本16.8 今天就来试试。我们很乐意收到您的来信,帮助我们确定优先级并为您构建合适的功能。我们可以通过以下评论联系到您, 开发者社区 , 还有推特( @视觉 ). 提交bug或建议特性的最佳方法是通过开发者社区。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享