【问题标题】:Is There A Way To Remove Duplicate Code While Providing lvalue and rvalue Overloads?有没有办法在提供左值和右值重载时删除重复代码?
【发布时间】:2020-02-18 19:49:58
【问题描述】:

在学习 C++ 时,我决定编写一个简单的模板化二叉搜索树 (bst),但遇到了以下问题:我希望能够构造一个 bst,方法是向它传递一个像 @987654322 这样的左值@ 和像 T &&val 这样的右值。同样,我希望能够插入左值和右值。所以我最终得到了很多我不喜欢的重复代码:

/// copy constructor
explicit inline constexpr binary_search_tree(const T &val)
    : _root{std::make_unique<binary_search_tree_node>(val)} {}

/// move constructor
explicit inline constexpr binary_search_tree(T &&val)
    : _root{std::make_unique<binary_search_tree_node>(std::move(val))} {}

对于构造函数,其中binary_search_tree_nodebinary_search_tree 的私有成员,它还必须提供复制和移动构造函数:

struct binary_search_tree_node {
  T value;
  std::unique_ptr<binary_search_tree_node> left;
  std::unique_ptr<binary_search_tree_node> right;

  // prohibit creation of tree_node without value
  inline constexpr binary_search_tree_node() = delete;

  /// copy constructor
  explicit inline constexpr binary_search_tree_node(const T &val)
      : value{val}, left{nullptr}, right{nullptr} {}

  /// move constructor
  explicit inline constexpr binary_search_tree_node(T &&val)
      : value{std::move(val)}, left{nullptr}, right{nullptr} {}
};

还有:

inline constexpr void insert(const T &v) {
  if (!_root) {
    _root = std::make_unique<binary_search_tree_node>(v);
    ++_size;
  } else {
    insert(_root, v);
  }
}

inline constexpr void insert(T &&v) {
  if (!_root) {
    _root = std::make_unique<binary_search_tree_node>(std::move(v));
    ++_size;
  } else {
    insert(_root, std::move(v));
  }
}

用于插入功能。

当我想搜索一个值时,列表继续:我应该为find(const T &amp;val) find(T &amp;&amp;val)..提供重载吗?

所以我的问题是是否有一种方法可以组合这些重载或任何 其他方式来删除这个重复的代码?

我读到了reference collapsing rules,但我不确定我是否可以在这里使用这个概念。

也欢迎任何其他想法或建议。

【问题讨论】:

  • 小注:inlineconstexpr函数的情况下是多余的。

标签: c++ overloading code-duplication rvalue lvalue


【解决方案1】:

是的,您可以使用引用折叠来减少编写的函数数量。

例如,您的函数 insert 可以使用右值引用 + 立即上下文来利用引用折叠,从而产生转发引用:

template<typename U>
inline constexpr void insert(U &&v) { // v is a forwarding reference
    if (!_root) {
        // we use std::forward to keep rvalue-ness
        // of the named object when v is an rvalue, but not when it's a lvalue
        _root = std::make_unique<binary_search_tree_node>(std::forward<U>(v));
        ++_size;
    } else {
        insert(_root, std::forward<U>(v));
    }
}

即使参数使用的是右值引用,由于引用折叠,左值也会在这里起作用。如果U 推导出为int&amp;,那么参数就是int&amp; &amp;&amp;,它折叠为int&amp;。相反,如果发送一个右值,它会将int推导出为U,因此参数为int &amp;&amp;

这种模式称为转发引用。

如您所见,如果转发参数存在模板参数推导,则此类代码仅具有该属性。

请注意,如果没有正确约束,将其设为模板将使其接受比预期更多的类型。

您也可以使用复制然后移动以避免代码重复:

inline constexpr void insert(const T &v) {
    T copy = v;
    insert(std::move(copy)); // calls the rvalue overload
}

【讨论】:

  • [ProTip] 将静态断言添加到转发引用以确保 U 是预期的类型。这样你就可以帮自己一个忙,得到一个易于理解的诊断,而不是当你将错误的东西传递给 insert 时编译器会给你什么
  • 你也可以insert(T v) { insert(std::move(v));} 省去手动复制
  • @NathanOliver 所以当我想插入一个像int value{3}; 这样的左值然后bst.insert(value) 我自己必须移动 value 到插入函数中,对吗?换句话说:如果我想在获得左值时创建该值的副本,并在获得右值时移动它,我想我不会在不声明这两个函数的情况下逃脱?
  • @SebastianWilke 不完全是。如果您将value 移动到insert,您将使用右值版本插入。转发版本将为您节省重复的代码,但会编译成两个(或更多)不同的函数。复制 + 移动将始终移动到插入功能,但会在之前复制。它不是从左值直接构造的,因为您将副本作为右值传递,但应该产生相同的结果。
  • @GuillaumeRacicot 所以你的建议是both 你在回答中提到的功能?否则我看不到 only 更高版本如何接受左值,因为它使用T &amp;&amp;v
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-07
  • 1970-01-01
  • 2022-06-15
  • 1970-01-01
  • 2021-08-25
  • 1970-01-01
相关资源
最近更新 更多