【问题标题】:Const correctness of STL library with reference-wrapper?带有引用包装器的 STL 库的 const 正确性?
【发布时间】:2019-09-18 13:16:43
【问题描述】:

我有以下课程:

class Data;
class A
{
public:
    A(Data& _data) : data(_data) {}
    Data& getData() {return data;}
    const Data& getData() const {return data;}
private:
    Data& data;
};

现在想象一下,我需要保留的不是一个,而是多个 Data 实例。我将它们保存在引用包装器的向量中,但我也想保持 const 正确性:在 const 上下文中将数据作为不可修改的方式传递。

class A
{
public:
    void addData(Data& _data) {data.push_back(std::ref(_data));}
    const std::vector<std::reference_wrapper<Data>>& getData() {return data;}
    //doesn't compile
    //const std::vector<std::reference_wrapper<const Data>>& getData() const {return data;}
private:
    std::vector<std::reference_wrapper<Data>> data;
}

如何在没有物理复制数据的情况下实现这一点? IE。我不想按值返回向量的副本,也不想在 A 类中保留两个单独的向量。对于基本上只是语义问题的问题,两者都是影响性能的解决方案。

【问题讨论】:

  • 你想将Data对象保留在A内部的可变向量中吗?
  • 为reference_wrapper制作你自己的常量传播替代品?或者,不是返回对整个向量的引用,而是创建一个(const-correct)函数,返回对单个向量元素的引用(按索引)。
  • @NathanOliver 是的,我也希望能够将数据作为非常量传递。
  • 我会简单地使用重新解释强制转换为对 const 引用包装器向量的引用。
  • @ArneJ Nooooooooo

标签: c++ stl constants reference-wrapper


【解决方案1】:

这是一个传播 reference_wrapper 的常量,基于 cppreference's possible implementation

#include <utility>
#include <functional>
#include <type_traits>

namespace detail {
template <class T> T& FUN(T& t) noexcept { return t; }
template <class T> void FUN(T&&) = delete;
}

template <class T>
class reference_wrapper {
public:
  // types
  typedef T type;

  // construct/copy/destroy
  template <class U, class = decltype(
    detail::FUN<T>(std::declval<U>()),
    std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>> && !std::is_same_v<reference_wrapper<const T>, std::remove_cvref_t<U>>>()
  )>
  reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u))))
    : _ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {}
  reference_wrapper(reference_wrapper&) noexcept = default;
  reference_wrapper(reference_wrapper&&) noexcept = default;

  // assignment
  reference_wrapper& operator=(reference_wrapper& x) noexcept = default;
  reference_wrapper& operator=(reference_wrapper&& x) noexcept = default;

  // access
  operator T& () noexcept { return *_ptr; }
  T& get() noexcept { return *_ptr; }
  operator const T& () const noexcept { return *_ptr; }
  const T& get() const noexcept { return *_ptr; }

  template< class... ArgTypes >
  std::invoke_result_t<T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }

  template< class... ArgTypes >
  std::invoke_result_t<const T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) const {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }

private:
  T* _ptr;
};

template <class T>
class reference_wrapper<const T> {
public:
  // types
  typedef const T type;

  // construct/copy/destroy
  template <class U, class = decltype(
    detail::FUN<const T>(std::declval<U>()),
    std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>> && !std::is_same_v<reference_wrapper<T>, std::remove_cvref_t<U>>>()
  )>
  reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<const T>(std::forward<U>(u))))
    : _ptr(std::addressof(detail::FUN<const T>(std::forward<U>(u)))) {}
  reference_wrapper(const reference_wrapper<T>& o) noexcept 
    : _ptr(std::addressof(o.get())) {}
  reference_wrapper(const reference_wrapper&) noexcept = default;
  reference_wrapper(reference_wrapper&&) noexcept = default;

  // assignment
  reference_wrapper& operator=(const reference_wrapper& x) noexcept = default;
  reference_wrapper& operator=(reference_wrapper&& x) noexcept = default;

  // access
  operator const T& () const noexcept { return *_ptr; }
  const T& get() const noexcept { return *_ptr; }

  template< class... ArgTypes >
  std::invoke_result_t<const T&, ArgTypes...>
    operator() ( ArgTypes&&... args ) const {
    return std::invoke(get(), std::forward<ArgTypes>(args)...);
  }

private:
  const T* _ptr;
};

// deduction guides
template<class T>
reference_wrapper(T&) -> reference_wrapper<T>;

然后您可以通过span 添加 const 限定访问权限。

class A
{
public:
    void addData(Data& _data) {data.emplace_back(_data);}
    std::span<reference_wrapper<Data>> getData() { return { data.data(), data.size() }; }
    std::span<const reference_wrapper<Data>> getData() const { return { data.data(), data.size() }; }
private:
    std::vector<reference_wrapper<Data>> data;
}

请注意,您不能从第二个getData 复制或移动const reference_wrapper&lt;Data&gt;s,并且只能访问const Data &amp;。

【讨论】:

  • 目前还不清楚它有什么帮助。 OP 希望在所有情况下都返回对向量的 const 引用。人们需要一个向量包装器,它可以将常量(或非常量!)传播到向量元素,而不管向量本身的常量。
  • @n.m.我没注意到是const vector &amp;,谢谢。在这种情况下,span 是 vector 的合理替代品
  • 您的第二个 getData 返回非常量数据的 reference_wrapper。我需要数据是常量。
  • @JakubZaverka 我已经解决了复制const reference_wrapper的问题,现在只有const通过getData() const访问
【解决方案2】:

考虑访问者模式:

struct ConstVisitor {
  virtual ~ConstVisitor() = default;
  virtual bool visit(const Data & data) = 0;//returns true if search should keep going on
};

void A::accept(ConstVisitor & visitor) const;

这样对外界来说,数据存储在什么样的容器中(这里是std::vector)并不重要。访问者模式与 C# 中的枚举器非常相似。

【讨论】:

    猜你喜欢
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-09
    • 1970-01-01
    • 2013-04-24
    • 1970-01-01
    相关资源
    最近更新 更多